summaryrefslogtreecommitdiffstats
path: root/src/plugins/quota
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/plugins/quota
parentInitial commit. (diff)
downloaddovecot-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 'src/plugins/quota')
-rw-r--r--src/plugins/quota/Makefile.am144
-rw-r--r--src/plugins/quota/Makefile.in1179
-rw-r--r--src/plugins/quota/doveadm-quota.c165
-rw-r--r--src/plugins/quota/quota-count.c400
-rw-r--r--src/plugins/quota/quota-dict.c269
-rw-r--r--src/plugins/quota/quota-dirsize.c232
-rw-r--r--src/plugins/quota/quota-fs.c970
-rw-r--r--src/plugins/quota/quota-fs.h51
-rw-r--r--src/plugins/quota/quota-imapc.c494
-rw-r--r--src/plugins/quota/quota-maildir.c953
-rw-r--r--src/plugins/quota/quota-plugin.c31
-rw-r--r--src/plugins/quota/quota-plugin.h36
-rw-r--r--src/plugins/quota/quota-private.h230
-rw-r--r--src/plugins/quota/quota-status-settings.c37
-rw-r--r--src/plugins/quota/quota-status-settings.h10
-rw-r--r--src/plugins/quota/quota-status.c360
-rw-r--r--src/plugins/quota/quota-storage.c780
-rw-r--r--src/plugins/quota/quota-util.c465
-rw-r--r--src/plugins/quota/quota.c1543
-rw-r--r--src/plugins/quota/quota.h147
-rw-r--r--src/plugins/quota/rquota.x139
-rw-r--r--src/plugins/quota/test-quota-util.c96
22 files changed, 8731 insertions, 0 deletions
diff --git a/src/plugins/quota/Makefile.am b/src/plugins/quota/Makefile.am
new file mode 100644
index 0000000..e8bad8c
--- /dev/null
+++ b/src/plugins/quota/Makefile.am
@@ -0,0 +1,144 @@
+doveadm_moduledir = $(moduledir)/doveadm
+
+pkglibexecdir = $(libexecdir)/dovecot
+pkglibexec_PROGRAMS = quota-status
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-program-client \
+ -I$(top_srcdir)/src/doveadm \
+ $(LIBTIRPC_CFLAGS)
+
+NOPLUGIN_LDFLAGS =
+lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version
+lib10_quota_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+ lib10_quota_plugin.la
+
+quota_dist_sources = \
+ quota.c \
+ quota-count.c \
+ quota-fs.c \
+ quota-dict.c \
+ quota-dirsize.c \
+ quota-imapc.c \
+ quota-maildir.c \
+ quota-plugin.c \
+ quota-storage.c \
+ quota-util.c
+
+quota_common_objects = \
+ quota.lo \
+ quota-count.lo \
+ quota-fs.lo \
+ quota-dict.lo \
+ quota-dirsize.lo \
+ quota-imapc.lo \
+ quota-maildir.lo \
+ quota-plugin.lo \
+ quota-storage.lo \
+ quota-util.lo \
+ $(RQUOTA_XDR_LO)
+
+lib10_quota_plugin_la_SOURCES = $(quota_dist_sources)
+nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR)
+lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS)
+
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_quota_plugin.la
+
+lib10_doveadm_quota_plugin_la_SOURCES = \
+ doveadm-quota.c
+
+quota_status_SOURCES = \
+ quota-status.c \
+ quota-status-settings.c
+
+quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+quota_status_LDADD = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT) \
+ $(QUOTA_LIBS) \
+ $(BINARY_LDFLAGS)
+quota_status_DEPENDENCIES = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+if HAVE_RQUOTA
+RQUOTA_XDR = rquota_xdr.c
+RQUOTA_XDR_LO = rquota_xdr.lo
+#RQUOTA_X = /usr/include/rpcsvc/rquota.x
+RQUOTA_X = $(srcdir)/rquota.x
+rquota_xdr.c: Makefile rquota.h
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \
+ fi; \
+ (echo '#include "lib.h"'; \
+ echo '#undef FALSE'; \
+ echo '#undef TRUE'; \
+ echo '#include <rpc/rpc.h>'; \
+ $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \
+ sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \
+ -e 's,!xdr_,0 == xdr_,' \
+ -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \
+ -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \
+ -e 's/^static char rcsid.*//' ) > rquota_xdr.c
+
+rquota.h: Makefile $(RQUOTA_X)
+ $(RPCGEN) -h $(RQUOTA_X) > rquota.h
+
+quota-fs.lo: rquota.h
+
+endif
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = \
+ quota.h \
+ quota-fs.h \
+ quota-plugin.h \
+ quota-private.h
+noinst_HEADERS = \
+ quota-status-settings.h
+
+EXTRA_DIST = rquota.x
+
+clean-generic:
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ rm -f $(top_builddir)/src/plugins/quota/rquota.x; \
+ fi; \
+ rm -f rquota_xdr.c rquota.h
+
+test_programs = \
+ test-quota-util
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib/liblib.la
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+
+test_quota_util_SOURCES = test-quota-util.c
+test_quota_util_LDADD = quota-util.lo $(test_libs)
+test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/plugins/quota/Makefile.in b/src/plugins/quota/Makefile.in
new file mode 100644
index 0000000..a8a824f
--- /dev/null
+++ b/src/plugins/quota/Makefile.in
@@ -0,0 +1,1179 @@
+# 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@
+pkglibexec_PROGRAMS = quota-status$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/plugins/quota
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-quota-util$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \
+ "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(doveadm_module_LTLIBRARIES) $(module_LTLIBRARIES)
+lib10_doveadm_quota_plugin_la_LIBADD =
+am_lib10_doveadm_quota_plugin_la_OBJECTS = doveadm-quota.lo
+lib10_doveadm_quota_plugin_la_OBJECTS = \
+ $(am_lib10_doveadm_quota_plugin_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 =
+lib10_doveadm_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) \
+ $(lib10_doveadm_quota_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+am__DEPENDENCIES_1 =
+lib10_quota_plugin_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__objects_1 = quota.lo quota-count.lo quota-fs.lo quota-dict.lo \
+ quota-dirsize.lo quota-imapc.lo quota-maildir.lo \
+ quota-plugin.lo quota-storage.lo quota-util.lo
+am_lib10_quota_plugin_la_OBJECTS = $(am__objects_1)
+@HAVE_RQUOTA_TRUE@am__objects_2 = rquota_xdr.lo
+nodist_lib10_quota_plugin_la_OBJECTS = $(am__objects_2)
+lib10_quota_plugin_la_OBJECTS = $(am_lib10_quota_plugin_la_OBJECTS) \
+ $(nodist_lib10_quota_plugin_la_OBJECTS)
+lib10_quota_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lib10_quota_plugin_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_quota_status_OBJECTS = quota_status-quota-status.$(OBJEXT) \
+ quota_status-quota-status-settings.$(OBJEXT)
+quota_status_OBJECTS = $(am_quota_status_OBJECTS)
+am_test_quota_util_OBJECTS = test-quota-util.$(OBJEXT)
+test_quota_util_OBJECTS = $(am_test_quota_util_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/doveadm-quota.Plo \
+ ./$(DEPDIR)/quota-count.Plo ./$(DEPDIR)/quota-dict.Plo \
+ ./$(DEPDIR)/quota-dirsize.Plo ./$(DEPDIR)/quota-fs.Plo \
+ ./$(DEPDIR)/quota-imapc.Plo ./$(DEPDIR)/quota-maildir.Plo \
+ ./$(DEPDIR)/quota-plugin.Plo ./$(DEPDIR)/quota-storage.Plo \
+ ./$(DEPDIR)/quota-util.Plo ./$(DEPDIR)/quota.Plo \
+ ./$(DEPDIR)/quota_status-quota-status-settings.Po \
+ ./$(DEPDIR)/quota_status-quota-status.Po \
+ ./$(DEPDIR)/rquota_xdr.Plo ./$(DEPDIR)/test-quota-util.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 = $(lib10_doveadm_quota_plugin_la_SOURCES) \
+ $(lib10_quota_plugin_la_SOURCES) \
+ $(nodist_lib10_quota_plugin_la_SOURCES) \
+ $(quota_status_SOURCES) $(test_quota_util_SOURCES)
+DIST_SOURCES = $(lib10_doveadm_quota_plugin_la_SOURCES) \
+ $(lib10_quota_plugin_la_SOURCES) $(quota_status_SOURCES) \
+ $(test_quota_util_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+pkglibexecdir = $(libexecdir)/dovecot
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+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@
+doveadm_moduledir = $(moduledir)/doveadm
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-master \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-smtp \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-client \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/lib-storage/index/imapc \
+ -I$(top_srcdir)/src/lib-storage/index/maildir \
+ -I$(top_srcdir)/src/lib-program-client \
+ -I$(top_srcdir)/src/doveadm \
+ $(LIBTIRPC_CFLAGS)
+
+lib10_doveadm_quota_plugin_la_LDFLAGS = -module -avoid-version
+lib10_quota_plugin_la_LDFLAGS = -module -avoid-version
+module_LTLIBRARIES = \
+ lib10_quota_plugin.la
+
+quota_dist_sources = \
+ quota.c \
+ quota-count.c \
+ quota-fs.c \
+ quota-dict.c \
+ quota-dirsize.c \
+ quota-imapc.c \
+ quota-maildir.c \
+ quota-plugin.c \
+ quota-storage.c \
+ quota-util.c
+
+quota_common_objects = \
+ quota.lo \
+ quota-count.lo \
+ quota-fs.lo \
+ quota-dict.lo \
+ quota-dirsize.lo \
+ quota-imapc.lo \
+ quota-maildir.lo \
+ quota-plugin.lo \
+ quota-storage.lo \
+ quota-util.lo \
+ $(RQUOTA_XDR_LO)
+
+lib10_quota_plugin_la_SOURCES = $(quota_dist_sources)
+nodist_lib10_quota_plugin_la_SOURCES = $(RQUOTA_XDR)
+lib10_quota_plugin_la_LIBADD = $(QUOTA_LIBS)
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_quota_plugin.la
+
+lib10_doveadm_quota_plugin_la_SOURCES = \
+ doveadm-quota.c
+
+quota_status_SOURCES = \
+ quota-status.c \
+ quota-status-settings.c
+
+quota_status_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS)
+quota_status_LDADD = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT) \
+ $(QUOTA_LIBS) \
+ $(BINARY_LDFLAGS)
+
+quota_status_DEPENDENCIES = \
+ $(quota_common_objects) \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+@HAVE_RQUOTA_TRUE@RQUOTA_XDR = rquota_xdr.c
+@HAVE_RQUOTA_TRUE@RQUOTA_XDR_LO = rquota_xdr.lo
+#RQUOTA_X = /usr/include/rpcsvc/rquota.x
+@HAVE_RQUOTA_TRUE@RQUOTA_X = $(srcdir)/rquota.x
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ quota.h \
+ quota-fs.h \
+ quota-plugin.h \
+ quota-private.h
+
+noinst_HEADERS = \
+ quota-status-settings.h
+
+EXTRA_DIST = rquota.x
+test_programs = \
+ test-quota-util
+
+test_libs = \
+ ../../lib-test/libtest.la \
+ ../../lib/liblib.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+test_quota_util_SOURCES = test-quota-util.c
+test_quota_util_LDADD = quota-util.lo $(test_libs)
+test_quota_util_DEPENDENCIES = quota-util.lo $(test_deps)
+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/plugins/quota/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/plugins/quota/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+install-doveadm_moduleLTLIBRARIES: $(doveadm_module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(doveadm_moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(doveadm_moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(doveadm_moduledir)"; \
+ }
+
+uninstall-doveadm_moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(doveadm_module_LTLIBRARIES)'; test -n "$(doveadm_moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(doveadm_moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(doveadm_moduledir)/$$f"; \
+ done
+
+clean-doveadm_moduleLTLIBRARIES:
+ -test -z "$(doveadm_module_LTLIBRARIES)" || rm -f $(doveadm_module_LTLIBRARIES)
+ @list='$(doveadm_module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-moduleLTLIBRARIES: $(module_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \
+ }
+
+uninstall-moduleLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \
+ done
+
+clean-moduleLTLIBRARIES:
+ -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES)
+ @list='$(module_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib10_doveadm_quota_plugin.la: $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_doveadm_quota_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_doveadm_quota_plugin_la_LINK) -rpath $(doveadm_moduledir) $(lib10_doveadm_quota_plugin_la_OBJECTS) $(lib10_doveadm_quota_plugin_la_LIBADD) $(LIBS)
+
+lib10_quota_plugin.la: $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_DEPENDENCIES) $(EXTRA_lib10_quota_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_quota_plugin_la_LINK) -rpath $(moduledir) $(lib10_quota_plugin_la_OBJECTS) $(lib10_quota_plugin_la_LIBADD) $(LIBS)
+
+quota-status$(EXEEXT): $(quota_status_OBJECTS) $(quota_status_DEPENDENCIES) $(EXTRA_quota_status_DEPENDENCIES)
+ @rm -f quota-status$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(quota_status_OBJECTS) $(quota_status_LDADD) $(LIBS)
+
+test-quota-util$(EXEEXT): $(test_quota_util_OBJECTS) $(test_quota_util_DEPENDENCIES) $(EXTRA_test_quota_util_DEPENDENCIES)
+ @rm -f test-quota-util$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_quota_util_OBJECTS) $(test_quota_util_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-count.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dict.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-dirsize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-fs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-imapc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-maildir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-plugin.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota-util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quota_status-quota-status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rquota_xdr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-quota-util.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 $@ $<
+
+quota_status-quota-status.o: quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.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) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.o `test -f 'quota-status.c' || echo '$(srcdir)/'`quota-status.c
+
+quota_status-quota-status.obj: quota-status.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status.Tpo -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status.Tpo $(DEPDIR)/quota_status-quota-status.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status.c' object='quota_status-quota-status.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) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status.obj `if test -f 'quota-status.c'; then $(CYGPATH_W) 'quota-status.c'; else $(CYGPATH_W) '$(srcdir)/quota-status.c'; fi`
+
+quota_status-quota-status-settings.o: quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.o -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.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) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.o `test -f 'quota-status-settings.c' || echo '$(srcdir)/'`quota-status-settings.c
+
+quota_status-quota-status-settings.obj: quota-status-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT quota_status-quota-status-settings.obj -MD -MP -MF $(DEPDIR)/quota_status-quota-status-settings.Tpo -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/quota_status-quota-status-settings.Tpo $(DEPDIR)/quota_status-quota-status-settings.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota-status-settings.c' object='quota_status-quota-status-settings.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) $(quota_status_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o quota_status-quota-status-settings.obj `if test -f 'quota-status-settings.c'; then $(CYGPATH_W) 'quota-status-settings.c'; else $(CYGPATH_W) '$(srcdir)/quota-status-settings.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(doveadm_moduledir)" "$(DESTDIR)$(moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+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-doveadm_moduleLTLIBRARIES clean-generic clean-libtool \
+ clean-moduleLTLIBRARIES clean-noinstPROGRAMS \
+ clean-pkglibexecPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/doveadm-quota.Plo
+ -rm -f ./$(DEPDIR)/quota-count.Plo
+ -rm -f ./$(DEPDIR)/quota-dict.Plo
+ -rm -f ./$(DEPDIR)/quota-dirsize.Plo
+ -rm -f ./$(DEPDIR)/quota-fs.Plo
+ -rm -f ./$(DEPDIR)/quota-imapc.Plo
+ -rm -f ./$(DEPDIR)/quota-maildir.Plo
+ -rm -f ./$(DEPDIR)/quota-plugin.Plo
+ -rm -f ./$(DEPDIR)/quota-storage.Plo
+ -rm -f ./$(DEPDIR)/quota-util.Plo
+ -rm -f ./$(DEPDIR)/quota.Plo
+ -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po
+ -rm -f ./$(DEPDIR)/quota_status-quota-status.Po
+ -rm -f ./$(DEPDIR)/rquota_xdr.Plo
+ -rm -f ./$(DEPDIR)/test-quota-util.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-doveadm_moduleLTLIBRARIES \
+ install-moduleLTLIBRARIES install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibexecPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/doveadm-quota.Plo
+ -rm -f ./$(DEPDIR)/quota-count.Plo
+ -rm -f ./$(DEPDIR)/quota-dict.Plo
+ -rm -f ./$(DEPDIR)/quota-dirsize.Plo
+ -rm -f ./$(DEPDIR)/quota-fs.Plo
+ -rm -f ./$(DEPDIR)/quota-imapc.Plo
+ -rm -f ./$(DEPDIR)/quota-maildir.Plo
+ -rm -f ./$(DEPDIR)/quota-plugin.Plo
+ -rm -f ./$(DEPDIR)/quota-storage.Plo
+ -rm -f ./$(DEPDIR)/quota-util.Plo
+ -rm -f ./$(DEPDIR)/quota.Plo
+ -rm -f ./$(DEPDIR)/quota_status-quota-status-settings.Po
+ -rm -f ./$(DEPDIR)/quota_status-quota-status.Po
+ -rm -f ./$(DEPDIR)/rquota_xdr.Plo
+ -rm -f ./$(DEPDIR)/test-quota-util.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-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ 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-doveadm_moduleLTLIBRARIES \
+ clean-generic clean-libtool clean-moduleLTLIBRARIES \
+ 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-data \
+ install-data-am install-doveadm_moduleLTLIBRARIES install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-moduleLTLIBRARIES install-pdf install-pdf-am \
+ install-pkginc_libHEADERS 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-doveadm_moduleLTLIBRARIES \
+ uninstall-moduleLTLIBRARIES uninstall-pkginc_libHEADERS \
+ uninstall-pkglibexecPROGRAMS
+
+.PRECIOUS: Makefile
+
+@HAVE_RQUOTA_TRUE@rquota_xdr.c: Makefile rquota.h
+@HAVE_RQUOTA_TRUE@ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+@HAVE_RQUOTA_TRUE@ cp $(RQUOTA_X) $(top_builddir)/src/plugins/quota/; \
+@HAVE_RQUOTA_TRUE@ fi; \
+@HAVE_RQUOTA_TRUE@ (echo '#include "lib.h"'; \
+@HAVE_RQUOTA_TRUE@ echo '#undef FALSE'; \
+@HAVE_RQUOTA_TRUE@ echo '#undef TRUE'; \
+@HAVE_RQUOTA_TRUE@ echo '#include <rpc/rpc.h>'; \
+@HAVE_RQUOTA_TRUE@ $(RPCGEN) -c $(top_builddir)/src/plugins/quota/rquota.x | \
+@HAVE_RQUOTA_TRUE@ sed -e 's/IXDR_PUT/(void)IXDR_PUT/g' \
+@HAVE_RQUOTA_TRUE@ -e 's,!xdr_,0 == xdr_,' \
+@HAVE_RQUOTA_TRUE@ -e 's,/usr/include/rpcsvc/rquota.h,rquota.h,' \
+@HAVE_RQUOTA_TRUE@ -e 's/int32_t \*buf/int32_t *buf ATTR_UNUSED/' \
+@HAVE_RQUOTA_TRUE@ -e 's/^static char rcsid.*//' ) > rquota_xdr.c
+
+@HAVE_RQUOTA_TRUE@rquota.h: Makefile $(RQUOTA_X)
+@HAVE_RQUOTA_TRUE@ $(RPCGEN) -h $(RQUOTA_X) > rquota.h
+
+@HAVE_RQUOTA_TRUE@quota-fs.lo: rquota.h
+
+clean-generic:
+ if [ "$(top_srcdir)" != "$(top_builddir)" ]; then \
+ rm -f $(top_builddir)/src/plugins/quota/rquota.x; \
+ fi; \
+ rm -f rquota_xdr.c rquota.h
+
+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/plugins/quota/doveadm-quota.c b/src/plugins/quota/doveadm-quota.c
new file mode 100644
index 0000000..8a42b22
--- /dev/null
+++ b/src/plugins/quota/doveadm-quota.c
@@ -0,0 +1,165 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-dir.h"
+#include "quota-plugin.h"
+#include "quota-private.h"
+#include "doveadm-print.h"
+#include "doveadm-mail.h"
+
+const char *doveadm_quota_plugin_version = DOVECOT_ABI_VERSION;
+
+void doveadm_quota_plugin_init(struct module *module);
+void doveadm_quota_plugin_deinit(void);
+
+static int cmd_quota_get_root(struct quota_root *root)
+{
+ const char *const *res;
+ const char *error;
+ uint64_t value, limit;
+ enum quota_get_result qret;
+ int ret = 0;
+
+ res = quota_root_get_resources(root);
+ for (; *res != NULL; res++) {
+ qret = quota_get_resource(root, "", *res, &value, &limit, &error);
+ doveadm_print(root->set->name);
+ doveadm_print(*res);
+ if (qret == QUOTA_GET_RESULT_LIMITED) {
+ doveadm_print_num(value);
+ doveadm_print_num(limit);
+ if (limit > 0)
+ doveadm_print_num(value*100 / limit);
+ else
+ doveadm_print("0");
+ } else if (qret == QUOTA_GET_RESULT_UNLIMITED) {
+ doveadm_print_num(value);
+ doveadm_print("-");
+ doveadm_print("0");
+ } else {
+ i_error("Failed to get quota resource %s: %s",
+ *res, error);
+ doveadm_print("error");
+ doveadm_print("error");
+ doveadm_print("error");
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static int
+cmd_quota_get_run(struct doveadm_mail_cmd_context *ctx,
+ struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct quota_root *const *root;
+
+ if (quser == NULL) {
+ i_error("Quota not enabled");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+
+ int ret = 0;
+ array_foreach(&quser->quota->roots, root)
+ if (cmd_quota_get_root(*root) < 0)
+ ret = -1;
+ if (ret < 0)
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+ return ret;
+}
+
+static void cmd_quota_get_init(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ const char *const args[] ATTR_UNUSED)
+{
+ doveadm_print_header("root", "Quota name", 0);
+ doveadm_print_header("type", "Type", 0);
+ doveadm_print_header("value", "Value",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+ doveadm_print_header("limit", "Limit",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+ doveadm_print_header("percent", "%",
+ DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY);
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_quota_get_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_quota_get_run;
+ ctx->v.init = cmd_quota_get_init;
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ return ctx;
+}
+
+static int
+cmd_quota_recalc_run(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED,
+ struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct quota_root *const *root;
+ struct quota_transaction_context trans;
+
+ if (quser == NULL) {
+ i_error("Quota not enabled");
+ doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+ return -1;
+ }
+
+ i_zero(&trans);
+ trans.quota = quser->quota;
+ trans.recalculate = QUOTA_RECALCULATE_FORCED;
+
+ array_foreach(&quser->quota->roots, root) {
+ const char *error;
+ if ((*root)->backend.v.update(*root, &trans, &error) < 0)
+ i_error("Recalculating quota failed: %s", error);
+ if ((*root)->backend.v.flush != NULL)
+ (*root)->backend.v.flush(*root);
+ }
+ return 0;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_quota_recalc_alloc(void)
+{
+ struct doveadm_mail_cmd_context *ctx;
+
+ ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+ ctx->v.run = cmd_quota_recalc_run;
+ return ctx;
+}
+
+static struct doveadm_cmd_ver2 quota_commands[] = {
+ {
+ .name = "quota get",
+ .usage = "",
+ .mail_cmd = cmd_quota_get_alloc,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+ },
+ {
+ .name = "quota recalc",
+ .usage = "",
+ .mail_cmd = cmd_quota_recalc_alloc,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAMS_END
+ }
+};
+
+void doveadm_quota_plugin_init(struct module *module ATTR_UNUSED)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(quota_commands); i++)
+ doveadm_cmd_register_ver2(&quota_commands[i]);
+}
+
+void doveadm_quota_plugin_deinit(void)
+{
+}
diff --git a/src/plugins/quota/quota-count.c b/src/plugins/quota/quota-count.c
new file mode 100644
index 0000000..00e25e6
--- /dev/null
+++ b/src/plugins/quota/quota-count.c
@@ -0,0 +1,400 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mailbox-list-iter.h"
+#include "quota-private.h"
+
+struct count_quota_root {
+ struct quota_root root;
+
+ struct timeval cache_timeval;
+ uint64_t cached_bytes, cached_count;
+};
+
+struct quota_mailbox_iter {
+ struct quota_root *root;
+ struct mail_namespace *ns;
+ unsigned int ns_idx;
+ struct mailbox_list_iterate_context *iter;
+ struct mailbox_info info;
+ const char *error;
+};
+
+extern struct quota_backend quota_backend_count;
+
+static int
+quota_count_mailbox(struct quota_root *root, struct mail_namespace *ns,
+ const char *vname, uint64_t *bytes, uint64_t *count,
+ enum quota_get_result *error_result_r,
+ const char **error_r)
+{
+ struct quota_rule *rule;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ enum mail_error error;
+ const char *errstr;
+ int ret;
+
+ rule = quota_root_rule_find(root->set, vname);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ return 0;
+ }
+
+ box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY);
+ if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0) {
+ /* quota doesn't exist for this mailbox/storage */
+ ret = 0;
+ } else if (mailbox_get_metadata(box, root->quota->set->vsizes ?
+ MAILBOX_METADATA_VIRTUAL_SIZE :
+ MAILBOX_METADATA_PHYSICAL_SIZE,
+ &metadata) < 0 ||
+ mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error == MAIL_ERROR_TEMP) {
+ *error_r = t_strdup_printf(
+ "Couldn't get size of mailbox %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ } else if (error == MAIL_ERROR_INUSE) {
+ /* started on background. don't log an error. */
+ *error_r = t_strdup_printf(
+ "Ongoing quota calculation blocked getting size of %s: %s",
+ vname, errstr);
+ *error_result_r = QUOTA_GET_RESULT_BACKGROUND_CALC;
+ ret = -1;
+ } else {
+ /* non-temporary error, e.g. ACLs denied access. */
+ ret = 0;
+ }
+ } else {
+ ret = 0;
+ *bytes += root->quota->set->vsizes ?
+ metadata.virtual_size : metadata.physical_size;
+ *count += status.messages;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static struct quota_mailbox_iter *
+quota_mailbox_iter_begin(struct quota_root *root)
+{
+ struct quota_mailbox_iter *iter;
+
+ iter = i_new(struct quota_mailbox_iter, 1);
+ iter->root = root;
+ iter->error = "";
+ return iter;
+}
+
+static int
+quota_mailbox_iter_deinit(struct quota_mailbox_iter **_iter,
+ const char **error_r)
+{
+ struct quota_mailbox_iter *iter = *_iter;
+ int ret = *iter->error != '\0' ? -1 : 0;
+
+ *_iter = NULL;
+
+ const char *error2 = "";
+ if (iter->iter != NULL) {
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ error2 = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ ret = -1;
+ }
+ }
+ if (ret < 0) {
+ const char *separator =
+ *iter->error != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf("%s%s%s",
+ iter->error, separator, error2);
+ }
+ i_free(iter);
+ return ret;
+}
+
+static const struct mailbox_info *
+quota_mailbox_iter_next(struct quota_mailbox_iter *iter)
+{
+ struct mail_namespace *const *namespaces;
+ const struct mailbox_info *info;
+ unsigned int count;
+
+ if (iter->iter == NULL) {
+ namespaces = array_get(&iter->root->quota->namespaces, &count);
+ do {
+ if (iter->ns_idx >= count)
+ return NULL;
+
+ iter->ns = namespaces[iter->ns_idx++];
+ } while (!quota_root_is_namespace_visible(iter->root, iter->ns));
+ iter->iter = mailbox_list_iter_init(iter->ns->list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES);
+ }
+ while ((info = mailbox_list_iter_next(iter->iter)) != NULL) {
+ if ((info->flags & (MAILBOX_NONEXISTENT |
+ MAILBOX_NOSELECT)) == 0)
+ return info;
+ }
+ if (mailbox_list_iter_deinit(&iter->iter) < 0) {
+ iter->error = t_strdup_printf(
+ "Listing namespace '%s' failed: %s",
+ iter->ns->prefix,
+ mailbox_list_get_last_internal_error(iter->ns->list, NULL));
+ }
+ if (iter->ns->prefix_len > 0 &&
+ (iter->ns->prefix_len != 6 ||
+ strncasecmp(iter->ns->prefix, "INBOX", 5) != 0)) {
+ /* if the namespace prefix itself exists, count it also */
+ iter->info.ns = iter->ns;
+ iter->info.vname = t_strndup(iter->ns->prefix,
+ iter->ns->prefix_len-1);
+ return &iter->info;
+ }
+ /* try the next namespace */
+ return quota_mailbox_iter_next(iter);
+}
+
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r,
+ enum quota_get_result *error_result_r, const char **error_r)
+{
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ const char *error1 = "", *error2 = "";
+ int ret = 1;
+
+ *bytes_r = *count_r = 0;
+ if (root->recounting)
+ return 0;
+ root->recounting = TRUE;
+
+ struct event_reason *reason = event_reason_begin("quota:count");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ if (quota_count_mailbox(root, info->ns, info->vname,
+ bytes_r, count_r, error_result_r,
+ &error1) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0) {
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ ret = -1;
+ }
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strconcat(error1, separator, error2, NULL);
+ }
+ event_reason_end(&reason);
+ root->recounting = FALSE;
+ return ret;
+}
+
+static enum quota_get_result
+quota_count_cached(struct count_quota_root *root,
+ uint64_t *bytes_r, uint64_t *count_r,
+ const char **error_r)
+{
+ int ret;
+
+ if (root->cache_timeval.tv_usec == ioloop_timeval.tv_usec &&
+ root->cache_timeval.tv_sec == ioloop_timeval.tv_sec &&
+ ioloop_timeval.tv_sec != 0) {
+ *bytes_r = root->cached_bytes;
+ *count_r = root->cached_count;
+ return QUOTA_GET_RESULT_LIMITED;
+ }
+
+ enum quota_get_result error_res;
+ ret = quota_count(&root->root, bytes_r, count_r, &error_res, error_r);
+ if (ret < 0) {
+ return error_res;
+ } else if (ret > 0) {
+ root->cache_timeval = ioloop_timeval;
+ root->cached_bytes = *bytes_r;
+ root->cached_count = *count_r;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static struct quota_root *count_quota_alloc(void)
+{
+ struct count_quota_root *root;
+
+ root = i_new(struct count_quota_root, 1);
+ return &root->root;
+}
+
+static int count_quota_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ if (!root->quota->set->vsizes) {
+ *error_r = "quota count backend requires quota_vsizes=yes";
+ return -1;
+ }
+ event_set_append_log_prefix(root->backend.event, "quota-count: ");
+
+ root->auto_updating = TRUE;
+ return quota_root_default_init(root, args, error_r);
+}
+
+static void count_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static const char *const *
+count_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL
+ };
+ return resources;
+}
+
+static enum quota_get_result
+count_quota_get_resource(struct quota_root *_root,
+ const char *name, uint64_t *value_r,
+ const char **error_r)
+{
+ struct count_quota_root *root = (struct count_quota_root *)_root;
+ uint64_t bytes, count;
+ enum quota_get_result ret;
+
+ ret = quota_count_cached(root, &bytes, &count, error_r);
+ if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ return ret;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = bytes;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *value_r = count;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int quota_count_recalculate_box(struct mailbox *box,
+ const char **error_r)
+{
+ struct mail_index_transaction *trans;
+ struct mailbox_metadata metadata;
+ struct mailbox_index_vsize vsize_hdr;
+ const char *errstr;
+ enum mail_error error;
+
+ if (mailbox_open(box) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error != MAIL_ERROR_TEMP) {
+ /* non-temporary error, e.g. ACLs denied access. */
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "Couldn't open mailbox %s: %s", box->vname, errstr);
+ return -1;
+ }
+
+ /* reset the vsize header first */
+ trans = mail_index_transaction_begin(box->view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ i_zero(&vsize_hdr);
+ mail_index_update_header_ext(trans, box->vsize_hdr_ext_id,
+ 0, &vsize_hdr, sizeof(vsize_hdr));
+ if (mail_index_transaction_commit(&trans) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't commit mail index transaction for %s: %s",
+ box->vname,
+ mail_index_get_error_message(box->view->index));
+ return -1;
+ }
+ /* getting the vsize now forces its recalculation */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_VIRTUAL_SIZE,
+ &metadata) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't get mailbox %s vsize: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ /* call sync to write the change to mailbox list index */
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0) {
+ *error_r = t_strdup_printf(
+ "Couldn't sync mailbox %s: %s", box->vname,
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+static int quota_count_recalculate(struct quota_root *root,
+ const char **error_r)
+{
+ struct event_reason *reason;
+ struct quota_mailbox_iter *iter;
+ const struct mailbox_info *info;
+ struct mailbox *box;
+ int ret = 0;
+ const char *error1 = "", *error2 = "";
+
+ reason = event_reason_begin("quota:recalculate");
+
+ iter = quota_mailbox_iter_begin(root);
+ while ((info = quota_mailbox_iter_next(iter)) != NULL) {
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (quota_count_recalculate_box(box, &error1) < 0)
+ ret = -1;
+ mailbox_free(&box);
+ }
+ if (quota_mailbox_iter_deinit(&iter, &error2) < 0)
+ ret = -1;
+ if (ret < 0) {
+ const char *separator =
+ *error1 != '\0' && *error2 != '\0' ? " and " : "";
+ *error_r = t_strdup_printf(
+ "quota-count: recalculate failed: %s%s%s",
+ error1, separator, error2);
+ }
+ event_reason_end(&reason);
+ return ret;
+}
+
+static int
+count_quota_update(struct quota_root *root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct count_quota_root *croot = (struct count_quota_root *)root;
+
+ croot->cache_timeval.tv_sec = 0;
+ if (ctx->recalculate == QUOTA_RECALCULATE_FORCED) {
+ if (quota_count_recalculate(root, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+struct quota_backend quota_backend_count = {
+ .name = "count",
+
+ .v = {
+ .alloc = count_quota_alloc,
+ .init = count_quota_init,
+ .deinit = count_quota_deinit,
+ .get_resources = count_quota_root_get_resources,
+ .get_resource = count_quota_get_resource,
+ .update = count_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-dict.c b/src/plugins/quota/quota-dict.c
new file mode 100644
index 0000000..02e444a
--- /dev/null
+++ b/src/plugins/quota/quota-dict.c
@@ -0,0 +1,269 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "dict.h"
+#include "mail-user.h"
+#include "mail-namespace.h"
+#include "quota-private.h"
+
+
+#define DICT_QUOTA_CURRENT_PATH DICT_PATH_PRIVATE"quota/"
+#define DICT_QUOTA_CURRENT_BYTES_PATH DICT_QUOTA_CURRENT_PATH"storage"
+#define DICT_QUOTA_CURRENT_COUNT_PATH DICT_QUOTA_CURRENT_PATH"messages"
+
+struct dict_quota_root {
+ struct quota_root root;
+ struct dict *dict;
+ struct timeout *to_update;
+ bool disable_unset;
+};
+
+extern struct quota_backend quota_backend_dict;
+
+static struct quota_root *dict_quota_alloc(void)
+{
+ struct dict_quota_root *root;
+
+ root = i_new(struct dict_quota_root, 1);
+ return &root->root;
+}
+
+static void handle_nounset_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct dict_quota_root *)_root)->disable_unset = TRUE;
+}
+
+static int dict_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+ struct dict_settings set;
+ const char *username, *p, *error;
+
+ event_set_append_log_prefix(_root->backend.event, "quota-dict: ");
+
+ const struct quota_param_parser dict_params[] = {
+ {.param_name = "no-unset", .param_handler = handle_nounset_param},
+ quota_param_hidden, quota_param_ignoreunlimited, quota_param_noenforcing, quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ p = args == NULL ? NULL : strchr(args, ':');
+ if (p == NULL) {
+ *error_r = "URI missing from parameters";
+ return -1;
+ }
+
+ username = t_strdup_until(args, p);
+ args = p+1;
+
+ if (quota_parse_parameters(_root, &args, error_r, dict_params, FALSE) < 0)
+ i_unreached();
+
+ if (*username == '\0')
+ username = _root->quota->user->username;
+
+ e_debug(_root->backend.event, "user=%s, uri=%s, noenforcing=%d",
+ username, args, _root->no_enforcing ? 1 : 0);
+
+ /* FIXME: we should use 64bit integer as datatype instead but before
+ it can actually be used don't bother */
+ i_zero(&set);
+ set.base_dir = _root->quota->user->set->base_dir;
+ set.event_parent = _root->quota->user->event;
+ if (dict_init(args, &set, &root->dict, &error) < 0) {
+ *error_r = t_strdup_printf("dict_init(%s) failed: %s", args, error);
+ return -1;
+ }
+ return 0;
+}
+
+static void dict_quota_deinit(struct quota_root *_root)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+
+ i_assert(root->to_update == NULL);
+
+ if (root->dict != NULL) {
+ dict_wait(root->dict);
+ dict_deinit(&root->dict);
+ }
+ i_free(root);
+}
+
+static const char *const *
+dict_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES, QUOTA_NAME_MESSAGES, NULL
+ };
+
+ return resources;
+}
+
+static enum quota_get_result
+dict_quota_count(struct dict_quota_root *root,
+ bool want_bytes, uint64_t *value_r,
+ const char **error_r)
+{
+ struct dict_transaction_context *dt;
+ struct event_reason *reason;
+ uint64_t bytes, count;
+ enum quota_get_result error_res;
+ const struct dict_op_settings *set;
+
+ reason = event_reason_begin("quota:recalculate");
+ int ret = quota_count(&root->root, &bytes, &count, &error_res, error_r);
+ event_reason_end(&reason);
+ if (ret < 0)
+ return error_res;
+
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ dt = dict_transaction_begin(root->dict, set);
+ /* these unsets are mainly necessary for pgsql, because its
+ trigger otherwise increases quota without deleting it.
+ but some people with other databases want to store the
+ quota usage among other data in the same row, which
+ shouldn't be deleted. */
+ if (!root->disable_unset) {
+ dict_unset(dt, DICT_QUOTA_CURRENT_BYTES_PATH);
+ dict_unset(dt, DICT_QUOTA_CURRENT_COUNT_PATH);
+ }
+ dict_set(dt, DICT_QUOTA_CURRENT_BYTES_PATH, dec2str(bytes));
+ dict_set(dt, DICT_QUOTA_CURRENT_COUNT_PATH, dec2str(count));
+
+ e_debug(root->root.backend.event, "Quota recalculated: "
+ "count=%"PRIu64" bytes=%"PRIu64, count, bytes);
+
+ dict_transaction_commit_async_nocallback(&dt);
+ *value_r = want_bytes ? bytes : count;
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static enum quota_get_result
+dict_quota_get_resource(struct quota_root *_root,
+ const char *name, uint64_t *value_r,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+ bool want_bytes;
+ int ret;
+ const struct dict_op_settings *set;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ want_bytes = TRUE;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ want_bytes = FALSE;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ const char *key, *value, *error;
+ key = want_bytes ? DICT_QUOTA_CURRENT_BYTES_PATH :
+ DICT_QUOTA_CURRENT_COUNT_PATH;
+ ret = dict_lookup(root->dict, set, unsafe_data_stack_pool,
+ key, &value, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf(
+ "dict_lookup(%s) failed: %s", key, error);
+ *value_r = 0;
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ intmax_t tmp;
+ /* recalculate quota if it's negative or if it wasn't found */
+ if (ret == 0 || str_to_intmax(value, &tmp) < 0)
+ tmp = -1;
+ if (tmp >= 0)
+ *value_r = tmp;
+ else
+ return dict_quota_count(root, want_bytes, value_r, error_r);
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static void dict_quota_recalc_timeout(struct dict_quota_root *root)
+{
+ uint64_t value;
+ const char *error;
+
+ timeout_remove(&root->to_update);
+ if (dict_quota_count(root, TRUE, &value, &error)
+ <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ e_error(root->root.backend.event,
+ "Recalculation failed: %s", error);
+}
+
+static void dict_quota_update_callback(const struct dict_commit_result *result,
+ struct dict_quota_root *root)
+{
+ if (result->ret == 0) {
+ /* row doesn't exist, need to recalculate it */
+ if (root->to_update == NULL)
+ root->to_update = timeout_add_short(0, dict_quota_recalc_timeout, root);
+ } else if (result->ret < 0) {
+ e_error(root->root.backend.event,
+ "Quota update failed: %s "
+ "- Quota is now desynced", result->error);
+ }
+}
+
+static int
+dict_quota_update(struct quota_root *_root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *) _root;
+ struct dict_transaction_context *dt;
+ uint64_t value;
+ const struct dict_op_settings *set;
+
+ if (ctx->recalculate != QUOTA_RECALCULATE_DONT) {
+ if (dict_quota_count(root, TRUE, &value, error_r)
+ <= QUOTA_GET_RESULT_INTERNAL_ERROR)
+ return -1;
+ } else {
+ set = mail_user_get_dict_op_settings(root->root.quota->user);
+ dt = dict_transaction_begin(root->dict, set);
+ if (ctx->bytes_used != 0) {
+ dict_atomic_inc(dt, DICT_QUOTA_CURRENT_BYTES_PATH,
+ ctx->bytes_used);
+ }
+ if (ctx->count_used != 0) {
+ dict_atomic_inc(dt, DICT_QUOTA_CURRENT_COUNT_PATH,
+ ctx->count_used);
+ }
+ dict_transaction_no_slowness_warning(dt);
+ dict_transaction_commit_async(&dt, dict_quota_update_callback,
+ root);
+ }
+ return 0;
+}
+
+static void dict_quota_flush(struct quota_root *_root)
+{
+ struct dict_quota_root *root = (struct dict_quota_root *)_root;
+
+ dict_wait(root->dict);
+ if (root->to_update != NULL) {
+ dict_quota_recalc_timeout(root);
+ dict_wait(root->dict);
+ }
+}
+
+struct quota_backend quota_backend_dict = {
+ .name = "dict",
+
+ .v = {
+ .alloc = dict_quota_alloc,
+ .init = dict_quota_init,
+ .deinit = dict_quota_deinit,
+ .get_resources = dict_quota_root_get_resources,
+ .get_resource = dict_quota_get_resource,
+ .update = dict_quota_update,
+ .flush = dict_quota_flush,
+ }
+};
diff --git a/src/plugins/quota/quota-dirsize.c b/src/plugins/quota/quota-dirsize.c
new file mode 100644
index 0000000..a8305d8
--- /dev/null
+++ b/src/plugins/quota/quota-dirsize.c
@@ -0,0 +1,232 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+/* Quota reporting based on simply summing sizes of all files in mailbox
+ together. */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "quota-private.h"
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct quota_count_path {
+ const char *path;
+ bool is_file;
+};
+ARRAY_DEFINE_TYPE(quota_count_path, struct quota_count_path);
+
+extern struct quota_backend quota_backend_dirsize;
+
+static struct quota_root *dirsize_quota_alloc(void)
+{
+ return i_new(struct quota_root, 1);
+}
+
+static int dirsize_quota_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ root->auto_updating = TRUE;
+ event_set_append_log_prefix(root->backend.event, "quota-dirsize: ");
+ return quota_root_default_init(root, args, error_r);
+}
+
+static void dirsize_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static const char *const *
+dirsize_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources[] = { QUOTA_NAME_STORAGE_KILOBYTES, NULL };
+
+ return resources;
+}
+
+static int get_dir_usage(const char *dir, uint64_t *value,
+ const char **error_r)
+{
+ DIR *dirp;
+ string_t *path;
+ struct dirent *d;
+ struct stat st;
+ unsigned int path_pos;
+ int ret;
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ if (errno == ENOENT)
+ return 0;
+
+ *error_r = t_strdup_printf("opendir(%s) failed: %m", dir);
+ return -1;
+ }
+
+ path = t_str_new(128);
+ str_append(path, dir);
+ str_append_c(path, '/');
+ path_pos = str_len(path);
+
+ ret = 0;
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
+ /* skip . and .. */
+ continue;
+ }
+
+ str_truncate(path, path_pos);
+ str_append(path, d->d_name);
+
+ if (lstat(str_c(path), &st) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ *error_r = t_strdup_printf("lstat(%s) failed: %m", dir);
+ ret = -1;
+ break;
+ } else if (S_ISDIR(st.st_mode)) {
+ if (get_dir_usage(str_c(path), value, error_r) < 0) {
+ ret = -1;
+ break;
+ }
+ } else {
+ *value += st.st_size;
+ }
+ }
+
+ (void)closedir(dirp);
+ return ret;
+}
+
+static int get_usage(const char *path, bool is_file, uint64_t *value_r,
+ const char **error_r)
+{
+ struct stat st;
+
+ if (is_file) {
+ if (lstat(path, &st) < 0) {
+ if (errno == ENOENT)
+ return 0;
+
+ *error_r = t_strdup_printf("lstat(%s) failed: %m", path);
+ return -1;
+ }
+ *value_r += st.st_size;
+ } else {
+ if (get_dir_usage(path, value_r, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void quota_count_path_add(ARRAY_TYPE(quota_count_path) *paths,
+ const char *path, bool is_file)
+{
+ struct quota_count_path *count_path;
+ unsigned int i, count;
+ size_t path_len;
+
+ path_len = strlen(path);
+ count_path = array_get_modifiable(paths, &count);
+ for (i = 0; i < count; ) {
+ if (strncmp(count_path[i].path, path,
+ strlen(count_path[i].path)) == 0) {
+ /* this path has already been counted */
+ return;
+ }
+ if (strncmp(count_path[i].path, path, path_len) == 0 &&
+ count_path[i].path[path_len] == '/') {
+ /* the new path contains the existing path.
+ drop it and see if there are more to drop. */
+ array_delete(paths, i, 1);
+ count_path = array_get_modifiable(paths, &count);
+ } else {
+ i++;
+ }
+ }
+
+ count_path = array_append_space(paths);
+ count_path->path = t_strdup(path);
+ count_path->is_file = is_file;
+}
+
+static int
+get_quota_root_usage(struct quota_root *root, uint64_t *value_r,
+ const char **error_r)
+{
+ struct mail_namespace *const *namespaces;
+ ARRAY_TYPE(quota_count_path) paths;
+ const struct quota_count_path *count_paths;
+ unsigned int i, count;
+ const char *path;
+ bool is_file;
+
+ t_array_init(&paths, 8);
+ namespaces = array_get(&root->quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(root, namespaces[i]))
+ continue;
+
+ is_file = mail_storage_is_mailbox_file(namespaces[i]->storage);
+ if (mailbox_list_get_root_path(namespaces[i]->list,
+ MAILBOX_LIST_PATH_TYPE_DIR, &path))
+ quota_count_path_add(&paths, path, FALSE);
+
+ /* INBOX may be in different path. */
+ if (mailbox_list_get_path(namespaces[i]->list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) > 0)
+ quota_count_path_add(&paths, path, is_file);
+ }
+
+ /* now sum up the found paths */
+ *value_r = 0;
+ count_paths = array_get(&paths, &count);
+ for (i = 0; i < count; i++) {
+ if (get_usage(count_paths[i].path, count_paths[i].is_file,
+ value_r, error_r) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static enum quota_get_result
+dirsize_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ int ret;
+
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0) {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+ ret = get_quota_root_usage(_root, value_r, error_r);
+
+ return ret < 0 ? QUOTA_GET_RESULT_INTERNAL_ERROR : QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+dirsize_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_dirsize = {
+ .name = "dirsize",
+
+ .v = {
+ .alloc = dirsize_quota_alloc,
+ .init = dirsize_quota_init,
+ .deinit = dirsize_quota_deinit,
+ .get_resources = dirsize_quota_root_get_resources,
+ .get_resource = dirsize_quota_get_resource,
+ .update = dirsize_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-fs.c b/src/plugins/quota/quota-fs.c
new file mode 100644
index 0000000..57620c1
--- /dev/null
+++ b/src/plugins/quota/quota-fs.c
@@ -0,0 +1,970 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+/* Only for reporting filesystem quota */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hostpid.h"
+#include "mountpoint.h"
+#include "quota-private.h"
+#include "quota-fs.h"
+
+#ifdef HAVE_FS_QUOTA
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#ifdef HAVE_LINUX_DQBLK_XFS_H
+# include <linux/dqblk_xfs.h>
+# define HAVE_XFS_QUOTA
+#elif defined (HAVE_XFS_XQM_H)
+# include <xfs/xqm.h> /* CentOS 4.x at least uses this */
+# define HAVE_XFS_QUOTA
+#endif
+
+#ifdef HAVE_RQUOTA
+# include "rquota.h"
+# define RQUOTA_GETQUOTA_TIMEOUT_SECS 10
+#endif
+
+#ifndef DEV_BSIZE
+# ifdef DQBSIZE
+# define DEV_BSIZE DQBSIZE /* AIX */
+# else
+# define DEV_BSIZE 512
+# endif
+#endif
+
+#ifdef HAVE_STRUCT_DQBLK_CURSPACE
+# define dqb_curblocks dqb_curspace
+#endif
+
+/* Very old sys/quota.h doesn't define _LINUX_QUOTA_VERSION at all, which means
+ it supports only v1 quota. However, new sys/quota.h (glibc 2.25) removes
+ support for v1 entirely and again it doesn't define it. I guess we can just
+ assume v2 now, and if someone still wants v1 support they can add
+ -D_LINUX_QUOTA_VERSION=1 to CFLAGS. */
+#ifndef _LINUX_QUOTA_VERSION
+# define _LINUX_QUOTA_VERSION 2
+#endif
+
+#define mount_type_is_nfs(mount) \
+ (strcmp((mount)->type, "nfs") == 0 || \
+ strcmp((mount)->type, "nfs4") == 0)
+
+struct fs_quota_mountpoint {
+ int refcount;
+
+ char *mount_path;
+ char *device_path;
+ char *type;
+ unsigned int block_size;
+
+#ifdef FS_QUOTA_SOLARIS
+ int fd;
+ char *path;
+#endif
+};
+
+struct fs_quota_root {
+ struct quota_root root;
+ char *storage_mount_path;
+
+ uid_t uid;
+ gid_t gid;
+ struct fs_quota_mountpoint *mount;
+
+ bool inode_per_mail:1;
+ bool user_disabled:1;
+ bool group_disabled:1;
+#ifdef FS_QUOTA_NETBSD
+ struct quotahandle *qh;
+#endif
+};
+
+extern struct quota_backend quota_backend_fs;
+
+static struct quota_root *fs_quota_alloc(void)
+{
+ struct fs_quota_root *root;
+
+ root = i_new(struct fs_quota_root, 1);
+ root->uid = geteuid();
+ root->gid = getegid();
+
+ return &root->root;
+}
+
+static void handle_user_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->group_disabled = TRUE;
+}
+
+static void handle_group_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->user_disabled = TRUE;
+}
+
+static void handle_inode_param(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ ((struct fs_quota_root *)_root)->inode_per_mail = TRUE;
+}
+
+static void handle_mount_param(struct quota_root *_root, const char *param_value)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ i_free(root->storage_mount_path);
+ root->storage_mount_path = i_strdup(param_value);
+}
+
+static int fs_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ const struct quota_param_parser fs_params[] = {
+ {.param_name = "user", .param_handler = handle_user_param},
+ {.param_name = "group", .param_handler = handle_group_param},
+ {.param_name = "mount=", .param_handler = handle_mount_param},
+ {.param_name = "inode_per_mail", .param_handler = handle_inode_param},
+ quota_param_hidden, quota_param_noenforcing, quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ event_set_append_log_prefix(_root->backend.event, "quota-fs: ");
+
+ if (quota_parse_parameters(_root, &args, error_r, fs_params, TRUE) < 0)
+ return -1;
+ _root->auto_updating = TRUE;
+ return 0;
+}
+
+static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount)
+{
+ if (--mount->refcount > 0)
+ return;
+
+#ifdef FS_QUOTA_SOLARIS
+ i_close_fd_path(&mount->fd, mount->path);
+ i_free(mount->path);
+#endif
+
+ i_free(mount->device_path);
+ i_free(mount->mount_path);
+ i_free(mount->type);
+ i_free(mount);
+}
+
+static void fs_quota_deinit(struct quota_root *_root)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+
+ if (root->mount != NULL)
+ fs_quota_mountpoint_free(root->mount);
+ i_free(root->storage_mount_path);
+ i_free(root);
+}
+
+static struct fs_quota_mountpoint *fs_quota_mountpoint_get(const char *dir)
+{
+ struct fs_quota_mountpoint *mount;
+ struct mountpoint point;
+ int ret;
+
+ ret = mountpoint_get(dir, default_pool, &point);
+ if (ret <= 0)
+ return NULL;
+
+ mount = i_new(struct fs_quota_mountpoint, 1);
+ mount->refcount = 1;
+ mount->device_path = point.device_path;
+ mount->mount_path = point.mount_path;
+ mount->type = point.type;
+ mount->block_size = point.block_size;
+#ifdef FS_QUOTA_SOLARIS
+ mount->fd = -1;
+#endif
+
+ if (mount_type_is_nfs(mount)) {
+ if (strchr(mount->device_path, ':') == NULL) {
+ e_error(quota_backend_fs.event,
+ "%s is not a valid NFS device path",
+ mount->device_path);
+ fs_quota_mountpoint_free(mount);
+ return NULL;
+ }
+ }
+ return mount;
+}
+
+#define QUOTA_ROOT_MATCH(root, mount) \
+ ((root)->root.backend.name == quota_backend_fs.name && \
+ ((root)->storage_mount_path == NULL || \
+ strcmp((root)->storage_mount_path, (mount)->mount_path) == 0))
+
+static struct fs_quota_root *
+fs_quota_root_find_mountpoint(struct quota *quota,
+ const struct fs_quota_mountpoint *mount)
+{
+ struct quota_root *const *roots;
+ struct fs_quota_root *empty = NULL;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ struct fs_quota_root *root = (struct fs_quota_root *)roots[i];
+ if (QUOTA_ROOT_MATCH(root, mount)) {
+ if (root->mount == NULL)
+ empty = root;
+ else if (strcmp(root->mount->mount_path,
+ mount->mount_path) == 0)
+ return root;
+ }
+ }
+ return empty;
+}
+
+static void
+fs_quota_mount_init(struct fs_quota_root *root,
+ struct fs_quota_mountpoint *mount, const char *dir)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+#ifdef FS_QUOTA_SOLARIS
+#ifdef HAVE_RQUOTA
+ if (mount_type_is_nfs(mount)) {
+ /* using rquota for this mount */
+ } else
+#endif
+ if (mount->path == NULL) {
+ mount->path = i_strconcat(mount->mount_path, "/quotas", NULL);
+ mount->fd = open(mount->path, O_RDONLY);
+ if (mount->fd == -1 && errno != ENOENT)
+ e_error(root->root.backend.event,
+ "open(%s) failed: %m", mount->path);
+ }
+#endif
+ root->mount = mount;
+
+ e_debug(root->root.backend.event, "fs quota add mailbox dir = %s", dir);
+ e_debug(root->root.backend.event, "fs quota block device = %s", mount->device_path);
+ e_debug(root->root.backend.event, "fs quota mount point = %s", mount->mount_path);
+ e_debug(root->root.backend.event, "fs quota mount type = %s", mount->type);
+
+ /* if there are more unused quota roots, copy this mount to them */
+ roots = array_get(&root->root.quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ root = (struct fs_quota_root *)roots[i];
+ if (QUOTA_ROOT_MATCH(root, mount) && root->mount == NULL) {
+ mount->refcount++;
+ root->mount = mount;
+ }
+ }
+}
+
+static void fs_quota_add_missing_mounts(struct quota *quota)
+{
+ struct fs_quota_mountpoint *mount;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ struct fs_quota_root *root = (struct fs_quota_root *)roots[i];
+
+ if (root->root.backend.name != quota_backend_fs.name ||
+ root->storage_mount_path == NULL || root->mount != NULL)
+ continue;
+
+ mount = fs_quota_mountpoint_get(root->storage_mount_path);
+ if (mount != NULL) {
+ fs_quota_mount_init(root, mount,
+ root->storage_mount_path);
+ }
+ }
+}
+
+static void fs_quota_namespace_added(struct quota *quota,
+ struct mail_namespace *ns)
+{
+ struct fs_quota_mountpoint *mount;
+ struct fs_quota_root *root;
+ const char *dir;
+
+ if (!mailbox_list_get_root_path(ns->list, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &dir))
+ mount = NULL;
+ else
+ mount = fs_quota_mountpoint_get(dir);
+ if (mount != NULL) {
+ root = fs_quota_root_find_mountpoint(quota, mount);
+ if (root != NULL && root->mount == NULL)
+ fs_quota_mount_init(root, mount, dir);
+ else
+ fs_quota_mountpoint_free(mount);
+ }
+
+ /* we would actually want to do this only once after all quota roots
+ are created, but there's no way to do this right now */
+ fs_quota_add_missing_mounts(quota);
+}
+
+static const char *const *
+fs_quota_root_get_resources(struct quota_root *_root)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ static const char *resources_kb[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ NULL
+ };
+ static const char *resources_kb_messages[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+
+ return root->inode_per_mail ? resources_kb_messages : resources_kb;
+}
+
+#if defined(FS_QUOTA_LINUX) || defined(FS_QUOTA_BSDAIX) || \
+ defined(FS_QUOTA_NETBSD) || defined(HAVE_RQUOTA)
+static void fs_quota_root_disable(struct fs_quota_root *root, bool group)
+{
+ if (group)
+ root->group_disabled = TRUE;
+ else
+ root->user_disabled = TRUE;
+}
+#endif
+
+#ifdef HAVE_RQUOTA
+static void
+rquota_get_result(const rquota *rq,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r)
+{
+ /* use soft limits if they exist, fallback to hard limits */
+
+ /* convert the results from blocks to bytes */
+ *bytes_value_r = (uint64_t)rq->rq_curblocks *
+ (uint64_t)rq->rq_bsize;
+ if (rq->rq_bsoftlimit != 0) {
+ *bytes_limit_r = (uint64_t)rq->rq_bsoftlimit *
+ (uint64_t)rq->rq_bsize;
+ } else {
+ *bytes_limit_r = (uint64_t)rq->rq_bhardlimit *
+ (uint64_t)rq->rq_bsize;
+ }
+
+ *count_value_r = rq->rq_curfiles;
+ if (rq->rq_fsoftlimit != 0)
+ *count_limit_r = rq->rq_fsoftlimit;
+ else
+ *count_limit_r = rq->rq_fhardlimit;
+}
+
+static int
+do_rquota_user(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct getquota_rslt result;
+ struct getquota_args args;
+ struct timeval timeout;
+ enum clnt_stat call_status;
+ CLIENT *cl;
+ struct fs_quota_mountpoint *mount = root->mount;
+ const char *host;
+ char *path;
+
+ path = strchr(mount->device_path, ':');
+ i_assert(path != NULL);
+
+ host = t_strdup_until(mount->device_path, path);
+ path++;
+
+ /* For NFSv4, we send the filesystem path without initial /. Server
+ prepends proper NFS pseudoroot automatically and uses this for
+ detection of NFSv4 mounts. */
+ if (strcmp(root->mount->type, "nfs4") == 0) {
+ while (*path == '/')
+ path++;
+ }
+
+ e_debug(root->root.backend.event, "host=%s, path=%s, uid=%s",
+ host, path, dec2str(root->uid));
+
+ /* clnt_create() polls for a while to establish a connection */
+ cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp");
+ if (cl == NULL) {
+ *error_r = t_strdup_printf(
+ "could not contact RPC service on %s", host);
+ return -1;
+ }
+
+ /* Establish some RPC credentials */
+ auth_destroy(cl->cl_auth);
+ cl->cl_auth = authunix_create_default();
+
+ /* make the rquota call on the remote host */
+ args.gqa_pathp = path;
+ args.gqa_uid = root->uid;
+
+ timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
+ timeout.tv_usec = 0;
+ call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
+ (xdrproc_t)xdr_getquota_args, (char *)&args,
+ (xdrproc_t)xdr_getquota_rslt, (char *)&result,
+ timeout);
+
+ /* the result has been deserialized, let the client go */
+ auth_destroy(cl->cl_auth);
+ clnt_destroy(cl);
+
+ if (call_status != RPC_SUCCESS) {
+ const char *rpc_error_msg = clnt_sperrno(call_status);
+
+ *error_r = t_strdup_printf(
+ "remote rquota call failed: %s",
+ rpc_error_msg);
+ return -1;
+ }
+
+ switch (result.status) {
+ case Q_OK: {
+ rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
+ bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r);
+ e_debug(root->root.backend.event, "uid=%s, bytes=%"PRIu64"/%"PRIu64" "
+ "files=%"PRIu64"/%"PRIu64,
+ dec2str(root->uid),
+ *bytes_value_r, *bytes_limit_r,
+ *count_value_r, *count_limit_r);
+ return 1;
+ }
+ case Q_NOQUOTA:
+ e_debug(root->root.backend.event, "uid=%s, limit=unlimited",
+ dec2str(root->uid));
+ fs_quota_root_disable(root, FALSE);
+ return 0;
+ case Q_EPERM:
+ *error_r = "permission denied to rquota service";
+ return -1;
+ default:
+ *error_r = t_strdup_printf(
+ "unrecognized status code (%d) from rquota service",
+ result.status);
+ return -1;
+ }
+}
+
+static int
+do_rquota_group(struct fs_quota_root *root ATTR_UNUSED,
+ uint64_t *bytes_value_r ATTR_UNUSED,
+ uint64_t *bytes_limit_r ATTR_UNUSED,
+ uint64_t *count_value_r ATTR_UNUSED,
+ uint64_t *count_limit_r ATTR_UNUSED,
+ const char **error_r)
+{
+#if defined(EXT_RQUOTAVERS) && defined(GRPQUOTA)
+ struct getquota_rslt result;
+ ext_getquota_args args;
+ struct timeval timeout;
+ enum clnt_stat call_status;
+ CLIENT *cl;
+ struct fs_quota_mountpoint *mount = root->mount;
+ const char *host;
+ char *path;
+
+ path = strchr(mount->device_path, ':');
+ i_assert(path != NULL);
+
+ host = t_strdup_until(mount->device_path, path);
+ path++;
+
+ e_debug(root->root.backend.event, "host=%s, path=%s, gid=%s",
+ host, path, dec2str(root->gid));
+
+ /* clnt_create() polls for a while to establish a connection */
+ cl = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp");
+ if (cl == NULL) {
+ *error_r = t_strdup_printf(
+ "could not contact RPC service on %s (group)", host);
+ return -1;
+ }
+
+ /* Establish some RPC credentials */
+ auth_destroy(cl->cl_auth);
+ cl->cl_auth = authunix_create_default();
+
+ /* make the rquota call on the remote host */
+ args.gqa_pathp = path;
+ args.gqa_id = root->gid;
+ args.gqa_type = GRPQUOTA;
+ timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS;
+ timeout.tv_usec = 0;
+
+ call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA,
+ (xdrproc_t)xdr_ext_getquota_args, (char *)&args,
+ (xdrproc_t)xdr_getquota_rslt, (char *)&result,
+ timeout);
+
+ /* the result has been deserialized, let the client go */
+ auth_destroy(cl->cl_auth);
+ clnt_destroy(cl);
+
+ if (call_status != RPC_SUCCESS) {
+ const char *rpc_error_msg = clnt_sperrno(call_status);
+
+ *error_r = t_strdup_printf(
+ "remote ext rquota call failed: %s", rpc_error_msg);
+ return -1;
+ }
+
+ switch (result.status) {
+ case Q_OK: {
+ rquota_get_result(&result.getquota_rslt_u.gqr_rquota,
+ bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r);
+ e_debug(root->root.backend.event, "gid=%s, bytes=%"PRIu64"/%"PRIu64" "
+ "files=%"PRIu64"/%"PRIu64,
+ dec2str(root->gid),
+ *bytes_value_r, *bytes_limit_r,
+ *count_value_r, *count_limit_r);
+ return 1;
+ }
+ case Q_NOQUOTA:
+ e_debug(root->root.backend.event, "gid=%s, limit=unlimited",
+ dec2str(root->gid));
+ fs_quota_root_disable(root, TRUE);
+ return 0;
+ case Q_EPERM:
+ *error_r = "permission denied to ext rquota service";
+ return -1;
+ default:
+ *error_r = t_strdup_printf(
+ "unrecognized status code (%d) from ext rquota service",
+ result.status);
+ return -1;
+ }
+#else
+ *error_r = "rquota not compiled with group support";
+ return -1;
+#endif
+}
+#endif
+
+#ifdef FS_QUOTA_LINUX
+static int
+fs_quota_get_linux(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ int type, id;
+
+ type = group ? GRPQUOTA : USRQUOTA;
+ id = group ? root->gid : root->uid;
+
+#ifdef HAVE_XFS_QUOTA
+ if (strcmp(root->mount->type, "xfs") == 0) {
+ struct fs_disk_quota xdqblk;
+
+ if (quotactl(QCMD(Q_XGETQUOTA, type),
+ root->mount->device_path,
+ id, (caddr_t)&xdqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "errno=%d, quotactl(Q_XGETQUOTA, %s) failed: %m",
+ errno, root->mount->device_path);
+ return -1;
+ }
+
+ /* values always returned in 512 byte blocks */
+ *bytes_value_r = xdqblk.d_bcount * 512ULL;
+ *bytes_limit_r = xdqblk.d_blk_softlimit * 512ULL;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = xdqblk.d_blk_hardlimit * 512ULL;
+ }
+ *count_value_r = xdqblk.d_icount;
+ *count_limit_r = xdqblk.d_ino_softlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = xdqblk.d_ino_hardlimit;
+ }
+ } else
+#endif
+ {
+ /* ext2, ext3 */
+ if (quotactl(QCMD(Q_GETQUOTA, type),
+ root->mount->device_path,
+ id, (caddr_t)&dqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->device_path);
+ if (errno == EINVAL) {
+ *error_r = t_strdup_printf("%s, "
+ "Dovecot was compiled with Linux quota "
+ "v%d support, try changing it "
+ "(CPPFLAGS=-D_LINUX_QUOTA_VERSION=%d configure)",
+ *error_r,
+ _LINUX_QUOTA_VERSION,
+ _LINUX_QUOTA_VERSION == 1 ? 2 : 1);
+ }
+ return -1;
+ }
+
+#if _LINUX_QUOTA_VERSION == 1
+ *bytes_value_r = dqblk.dqb_curblocks * 1024ULL;
+#else
+ *bytes_value_r = dqblk.dqb_curblocks;
+#endif
+ *bytes_limit_r = dqblk.dqb_bsoftlimit * 1024ULL;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = dqblk.dqb_bhardlimit * 1024ULL;
+ }
+ *count_value_r = dqblk.dqb_curinodes;
+ *count_limit_r = dqblk.dqb_isoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_ihardlimit;
+ }
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_BSDAIX
+static int
+fs_quota_get_bsdaix(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ int type, id;
+
+ type = group ? GRPQUOTA : USRQUOTA;
+ id = group ? root->gid : root->uid;
+
+ if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, type),
+ id, (void *)&dqblk) < 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->mount_path);
+ return -1;
+ }
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE;
+ }
+ *count_value_r = dqblk.dqb_curinodes;
+ *count_limit_r = dqblk.dqb_isoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_ihardlimit;
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_NETBSD
+static int
+fs_quota_get_netbsd(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct quotakey qk;
+ struct quotaval qv;
+ struct quotahandle *qh;
+ int ret;
+
+ if ((qh = quota_open(root->mount->mount_path)) == NULL) {
+ *error_r = t_strdup_printf("cannot open quota for %s: %m",
+ root->mount->mount_path);
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+
+ qk.qk_idtype = group ? QUOTA_IDTYPE_GROUP : QUOTA_IDTYPE_USER;
+ qk.qk_id = group ? root->gid : root->uid;
+
+ for (int i = 0; i < 2; i++) {
+ qk.qk_objtype = i == 0 ? QUOTA_OBJTYPE_BLOCKS : QUOTA_OBJTYPE_FILES;
+
+ if (quota_get(qh, &qk, &qv) != 0) {
+ if (errno == ESRCH) {
+ fs_quota_root_disable(root, group);
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->mount_path);
+ ret = -1;
+ break;
+ }
+ if (i == 0) {
+ *bytes_value_r = qv.qv_usage * DEV_BSIZE;
+ *bytes_limit_r = qv.qv_softlimit * DEV_BSIZE;
+ } else {
+ *count_value_r = qv.qv_usage;
+ *count_limit_r = qv.qv_softlimit;
+ }
+ ret = 1;
+ }
+ quota_close(qh);
+ return ret;
+}
+#endif
+
+#ifdef FS_QUOTA_HPUX
+static int
+fs_quota_get_hpux(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+
+ if (quotactl(Q_GETQUOTA, root->mount->device_path,
+ root->uid, &dqblk) < 0) {
+ if (errno == ESRCH) {
+ root->user_disabled = TRUE;
+ return 0;
+ }
+ *error_r = t_strdup_printf(
+ "quotactl(Q_GETQUOTA, %s) failed: %m",
+ root->mount->device_path);
+ return -1;
+ }
+
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks *
+ root->mount->block_size;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit *
+ root->mount->block_size;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit *
+ root->mount->block_size;
+ }
+ *count_value_r = dqblk.dqb_curfiles;
+ *count_limit_r = dqblk.dqb_fsoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_fhardlimit;
+ }
+ return 1;
+}
+#endif
+
+#ifdef FS_QUOTA_SOLARIS
+static int
+fs_quota_get_solaris(struct fs_quota_root *root,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ struct dqblk dqblk;
+ struct quotctl ctl;
+
+ if (root->mount->fd == -1)
+ return 0;
+
+ ctl.op = Q_GETQUOTA;
+ ctl.uid = root->uid;
+ ctl.addr = (caddr_t)&dqblk;
+ if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) {
+ *error_r = t_strdup_printf(
+ "ioctl(%s, Q_QUOTACTL) failed: %m",
+ root->mount->path);
+ return -1;
+ }
+ *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE;
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE;
+ if (*bytes_limit_r == 0) {
+ *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE;
+ }
+ *count_value_r = dqblk.dqb_curfiles;
+ *count_limit_r = dqblk.dqb_fsoftlimit;
+ if (*count_limit_r == 0) {
+ *count_limit_r = dqblk.dqb_fhardlimit;
+ }
+ return 1;
+}
+#endif
+
+static int
+fs_quota_get_resources(struct fs_quota_root *root, bool group,
+ uint64_t *bytes_value_r, uint64_t *bytes_limit_r,
+ uint64_t *count_value_r, uint64_t *count_limit_r,
+ const char **error_r)
+{
+ if (group) {
+ if (root->group_disabled)
+ return 0;
+ } else {
+ if (root->user_disabled)
+ return 0;
+ }
+#ifdef FS_QUOTA_LINUX
+ return fs_quota_get_linux(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#elif defined (FS_QUOTA_NETBSD)
+ return fs_quota_get_netbsd(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#elif defined (FS_QUOTA_BSDAIX)
+ return fs_quota_get_bsdaix(root, group, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#else
+ if (group) {
+ /* not supported */
+ return 0;
+ }
+#ifdef FS_QUOTA_HPUX
+ return fs_quota_get_hpux(root, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#else
+ return fs_quota_get_solaris(root, bytes_value_r, bytes_limit_r,
+ count_value_r, count_limit_r, error_r);
+#endif
+#endif
+}
+
+static bool fs_quota_match_box(struct quota_root *_root, struct mailbox *box)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ struct stat mst, rst;
+ const char *mailbox_path;
+ bool match;
+
+ if (root->storage_mount_path == NULL)
+ return TRUE;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) <= 0)
+ return FALSE;
+ if (stat(mailbox_path, &mst) < 0) {
+ if (errno != ENOENT)
+ e_error(_root->backend.event,
+ "stat(%s) failed: %m", mailbox_path);
+ return FALSE;
+ }
+ if (stat(root->storage_mount_path, &rst) < 0) {
+ e_debug(_root->backend.event, "stat(%s) failed: %m",
+ root->storage_mount_path);
+ return FALSE;
+ }
+ match = CMP_DEV_T(mst.st_dev, rst.st_dev);
+ e_debug(_root->backend.event, "box=%s mount=%s match=%s", mailbox_path,
+ root->storage_mount_path, match ? "yes" : "no");
+ return match;
+}
+
+static enum quota_get_result
+fs_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct fs_quota_root *root = (struct fs_quota_root *)_root;
+ uint64_t bytes_value, count_value;
+ uint64_t bytes_limit = 0, count_limit = 0;
+ int ret;
+
+ *value_r = 0;
+
+ if (root->mount == NULL) {
+ if (root->storage_mount_path != NULL)
+ *error_r = t_strdup_printf(
+ "Mount point unknown for path %s",
+ root->storage_mount_path);
+ else
+ *error_r = "Mount point unknown";
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0 &&
+ strcasecmp(name, QUOTA_NAME_MESSAGES) != 0) {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+
+#ifdef HAVE_RQUOTA
+ if (mount_type_is_nfs(root->mount)) {
+ ret = root->user_disabled ? 0 :
+ do_rquota_user(root, &bytes_value, &bytes_limit,
+ &count_value, &count_limit, error_r);
+ if (ret == 0 && !root->group_disabled)
+ ret = do_rquota_group(root, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ } else
+#endif
+ {
+ ret = fs_quota_get_resources(root, FALSE, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ if (ret == 0) {
+ /* fallback to group quota */
+ ret = fs_quota_get_resources(root, TRUE, &bytes_value,
+ &bytes_limit, &count_value,
+ &count_limit, error_r);
+ }
+ }
+ if (ret < 0)
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ else if (ret == 0)
+ return QUOTA_GET_RESULT_LIMITED;
+
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = bytes_value;
+ else
+ *value_r = count_value;
+ if (_root->bytes_limit != (int64_t)bytes_limit ||
+ _root->count_limit != (int64_t)count_limit) {
+ /* update limit */
+ _root->bytes_limit = bytes_limit;
+ _root->count_limit = count_limit;
+
+ /* limits have changed, so we'll need to recalculate
+ relative quota rules */
+ quota_root_recalculate_relative_rules(_root->set, bytes_limit, count_limit);
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+fs_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_fs = {
+ .name = "fs",
+
+ .v = {
+ .alloc = fs_quota_alloc,
+ .init = fs_quota_init,
+ .deinit = fs_quota_deinit,
+
+ .namespace_added = fs_quota_namespace_added,
+
+ .get_resources = fs_quota_root_get_resources,
+ .get_resource = fs_quota_get_resource,
+ .update = fs_quota_update,
+
+ .match_box = fs_quota_match_box,
+ }
+};
+
+#endif
diff --git a/src/plugins/quota/quota-fs.h b/src/plugins/quota/quota-fs.h
new file mode 100644
index 0000000..8508cdf
--- /dev/null
+++ b/src/plugins/quota/quota-fs.h
@@ -0,0 +1,51 @@
+#ifndef QUOTA_FS_H
+#define QUOTA_FS_H
+
+#if defined (HAVE_STRUCT_DQBLK_CURBLOCKS) || \
+ defined (HAVE_STRUCT_DQBLK_CURSPACE)
+# define HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTA_OPEN
+/* absolute path to avoid confusion with ./quota.h */
+# include "/usr/include/quota.h" /* NetBSD with libquota */
+#endif
+
+#ifdef HAVE_SYS_QUOTA_H
+# include <sys/quota.h> /* Linux, HP-UX */
+#elif defined(HAVE_SYS_FS_UFS_QUOTA_H)
+# include <sys/fs/ufs_quota.h> /* Solaris */
+#elif defined(HAVE_UFS_UFS_QUOTA_H)
+# include <ufs/ufs/quota.h> /* BSDs */
+#elif defined(HAVE_JFS_QUOTA_H)
+# include <jfs/quota.h> /* AIX */
+# ifdef HAVE_SYS_FS_QUOTA_COMMON_H
+# include <sys/fs/quota_common.h> /* quotactl() */
+# endif
+#else
+# undef HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTACTL
+# ifdef HAVE_SYS_QUOTA_H
+# ifndef _HPUX_SOURCE
+# define FS_QUOTA_LINUX
+# else
+# define FS_QUOTA_HPUX
+# endif
+# else
+# define FS_QUOTA_BSDAIX
+# endif
+#elif defined (HAVE_Q_QUOTACTL)
+# define FS_QUOTA_SOLARIS
+#else
+# undef HAVE_FS_QUOTA
+#endif
+
+#ifdef HAVE_QUOTA_OPEN /* NetBSD with libquota */
+# define FS_QUOTA_NETBSD
+# define HAVE_FS_QUOTA
+# undef FS_QUOTA_LINUX /* obtained because we also have <sys/quota.h> */
+#endif
+
+#endif
diff --git a/src/plugins/quota/quota-imapc.c b/src/plugins/quota/quota-imapc.c
new file mode 100644
index 0000000..d5931db
--- /dev/null
+++ b/src/plugins/quota/quota-imapc.c
@@ -0,0 +1,494 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "imap-arg.h"
+#include "imapc-storage.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+
+struct imapc_quota_refresh_root {
+ const char *name;
+ unsigned int order;
+
+ uint64_t bytes_cur, count_cur;
+ uint64_t bytes_limit, count_limit;
+};
+
+struct imapc_quota_refresh {
+ pool_t pool;
+ const char *box_name;
+ ARRAY(struct imapc_quota_refresh_root) roots;
+};
+
+struct imapc_quota_root {
+ struct quota_root root;
+ const char *box_name, *root_name;
+
+ struct mail_namespace *imapc_ns;
+ struct imapc_storage_client *client;
+ bool initialized;
+
+ uint64_t bytes_last, count_last;
+
+ struct timeval last_refresh;
+ struct imapc_quota_refresh refresh;
+};
+
+extern struct quota_backend quota_backend_imapc;
+
+static struct quota_root *imapc_quota_alloc(void)
+{
+ struct imapc_quota_root *root;
+
+ root = i_new(struct imapc_quota_root, 1);
+ return &root->root;
+}
+
+static void handle_box_param(struct quota_root *_root, const char *param_value)
+{
+ ((struct imapc_quota_root *)_root)->box_name = p_strdup(_root->pool, param_value);
+}
+
+static void handle_root_param(struct quota_root *_root, const char *param_value)
+{
+ ((struct imapc_quota_root *)_root)->root_name = p_strdup(_root->pool, param_value);
+}
+
+static int imapc_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+ const struct quota_param_parser imapc_params[] = {
+ {.param_name = "box=", .param_handler = handle_box_param},
+ {.param_name = "root=", .param_handler = handle_root_param},
+ quota_param_ns,
+ {.param_name = NULL}
+ };
+
+ _root->auto_updating = TRUE;
+ event_set_append_log_prefix(root->root.backend.event, "quota-imapc: ");
+
+ if (quota_parse_parameters(_root, &args, error_r, imapc_params, TRUE) < 0)
+ return -1;
+
+ if (root->box_name == NULL && root->root_name == NULL)
+ root->box_name = "INBOX";
+ /* we'll never try to enforce the quota - it's just a lot of
+ unnecessary remote GETQUOTA calls. */
+ _root->no_enforcing = TRUE;
+ return 0;
+}
+
+static void imapc_quota_deinit(struct quota_root *_root)
+{
+ i_free(_root);
+}
+
+static void
+imapc_quota_root_namespace_added(struct quota_root *_root,
+ struct mail_namespace *ns)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ if (root->imapc_ns == NULL)
+ root->imapc_ns = ns;
+}
+
+static struct imapc_quota_refresh *
+imapc_quota_root_refresh_find(struct imapc_storage_client *client)
+{
+ struct imapc_storage *storage = client->_storage;
+ struct quota *quota;
+ struct quota_root *const *rootp;
+
+ i_assert(storage != NULL);
+ quota = quota_get_mail_user_quota(storage->storage.user);
+ i_assert(quota != NULL);
+
+ /* find the quota root that is being refreshed */
+ array_foreach(&quota->roots, rootp) {
+ if ((*rootp)->backend.name == quota_backend_imapc.name) {
+ struct imapc_quota_root *root =
+ (struct imapc_quota_root *)*rootp;
+
+ if (root->refresh.pool != NULL)
+ return &root->refresh;
+ }
+ }
+ return NULL;
+}
+
+static struct imapc_quota_refresh_root *
+imapc_quota_refresh_root_get(struct imapc_quota_refresh *refresh,
+ const char *root_name)
+{
+ struct imapc_quota_refresh_root *refresh_root;
+
+ array_foreach_modifiable(&refresh->roots, refresh_root) {
+ if (strcmp(refresh_root->name, root_name) == 0)
+ return refresh_root;
+ }
+
+ refresh_root = array_append_space(&refresh->roots);
+ refresh_root->order = UINT_MAX;
+ refresh_root->name = p_strdup(refresh->pool, root_name);
+ refresh_root->bytes_limit = (uint64_t)-1;
+ refresh_root->count_limit = (uint64_t)-1;
+ return refresh_root;
+}
+
+static void imapc_untagged_quotaroot(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ struct imapc_quota_refresh *refresh;
+ struct imapc_quota_refresh_root *refresh_root;
+ const char *mailbox_name, *root_name;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &mailbox_name))
+ return;
+
+ if ((refresh = imapc_quota_root_refresh_find(client)) == NULL ||
+ refresh->box_name == NULL ||
+ strcmp(refresh->box_name, mailbox_name) != 0) {
+ /* unsolicited QUOTAROOT reply - ignore */
+ return;
+ }
+ if (array_count(&refresh->roots) > 0) {
+ /* duplicate QUOTAROOT reply - ignore */
+ return;
+ }
+
+ i = 1;
+ while (imap_arg_get_astring(&reply->args[i], &root_name)) {
+ refresh_root = imapc_quota_refresh_root_get(refresh, root_name);
+ refresh_root->order = i;
+ i++;
+ }
+}
+
+static void imapc_untagged_quota(const struct imapc_untagged_reply *reply,
+ struct imapc_storage_client *client)
+{
+ const struct imap_arg *list;
+ struct imapc_quota_refresh *refresh;
+ struct imapc_quota_refresh_root *refresh_root;
+ const char *root_name, *resource, *value_str, *limit_str;
+ uint64_t value, limit;
+ unsigned int i;
+
+ if (!imap_arg_get_astring(&reply->args[0], &root_name) ||
+ !imap_arg_get_list(&reply->args[1], &list))
+ return;
+
+ if ((refresh = imapc_quota_root_refresh_find(client)) == NULL) {
+ /* unsolicited QUOTA reply - ignore */
+ return;
+ }
+ refresh_root = imapc_quota_refresh_root_get(refresh, root_name);
+
+ for (i = 0; list[i].type != IMAP_ARG_EOL; i += 3) {
+ if (!imap_arg_get_atom(&list[i], &resource) ||
+ !imap_arg_get_atom(&list[i+1], &value_str) ||
+ !imap_arg_get_atom(&list[i+2], &limit_str) ||
+ /* RFC2087 uses 32bit number, but be ready for future */
+ str_to_uint64(value_str, &value) < 0 ||
+ str_to_uint64(limit_str, &limit) < 0)
+ return;
+
+ if (strcasecmp(resource, QUOTA_NAME_STORAGE_KILOBYTES) == 0) {
+ refresh_root->bytes_cur = value * 1024;
+ refresh_root->bytes_limit = limit * 1024;
+ } else if (strcasecmp(resource, QUOTA_NAME_MESSAGES) == 0) {
+ refresh_root->count_cur = value;
+ refresh_root->count_limit = limit;
+ }
+ }
+}
+
+static bool imapc_quota_client_init(struct imapc_quota_root *root)
+{
+ struct mailbox_list *list;
+ struct mail_storage *storage;
+
+ if (root->initialized)
+ return root->client != NULL;
+ root->initialized = TRUE;
+
+ list = root->imapc_ns->list;
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ strcmp(storage->name, IMAPC_STORAGE_NAME) != 0) {
+ /* non-imapc namespace, skip */
+ if ((storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) {
+ e_warning(root->root.backend.event,
+ "Namespace '%s' is not imapc, "
+ "skipping for imapc quota",
+ root->imapc_ns->prefix);
+ }
+ return FALSE;
+ }
+ root->client = ((struct imapc_storage *)storage)->client;
+
+ imapc_storage_client_register_untagged(root->client, "QUOTAROOT",
+ imapc_untagged_quotaroot);
+ imapc_storage_client_register_untagged(root->client, "QUOTA",
+ imapc_untagged_quota);
+ return TRUE;
+}
+
+static void imapc_quota_refresh_init(struct imapc_quota_refresh *refresh)
+{
+ i_assert(refresh->pool == NULL);
+
+ refresh->pool = pool_alloconly_create("imapc quota refresh", 256);
+ p_array_init(&refresh->roots, refresh->pool, 4);
+}
+
+static void
+imapc_quota_refresh_update(struct quota *quota,
+ struct imapc_quota_refresh *refresh)
+{
+ struct quota_root *const *rootp;
+ const struct imapc_quota_refresh_root *refresh_root;
+
+ if (array_count(&refresh->roots) == 0) {
+ e_error(quota_backend_imapc.event,
+ "imapc didn't return any QUOTA results");
+ return;
+ }
+ /* use the first quota root for everything */
+ refresh_root = array_front(&refresh->roots);
+
+ array_foreach(&quota->roots, rootp) {
+ if ((*rootp)->backend.name == quota_backend_imapc.name) {
+ struct imapc_quota_root *root =
+ (struct imapc_quota_root *)*rootp;
+
+ root->bytes_last = refresh_root->bytes_cur;
+ root->count_last = refresh_root->count_cur;
+
+ /* If limits are higher than what dovecot can handle
+ consider them unlimited. */
+ if (refresh_root->bytes_limit > INT64_MAX)
+ root->root.bytes_limit = 0;
+ else
+ root->root.bytes_limit = refresh_root->bytes_limit;
+ if (refresh_root->count_limit > INT64_MAX)
+ root->root.count_limit = 0;
+ else
+ root->root.count_limit = refresh_root->count_limit;
+ }
+ }
+}
+
+static void
+imapc_quota_refresh_deinit(struct quota *quota,
+ struct imapc_quota_refresh *refresh, bool success)
+{
+ if (success)
+ imapc_quota_refresh_update(quota, refresh);
+ pool_unref(&refresh->pool);
+ i_zero(refresh);
+}
+
+static int
+imapc_quota_refresh_root_order_cmp(const struct imapc_quota_refresh_root *root1,
+ const struct imapc_quota_refresh_root *root2)
+{
+ if (root1->order < root2->order)
+ return -1;
+ else if (root1->order > root2->order)
+ return 1;
+ else
+ return 0;
+}
+
+static int imapc_quota_refresh_mailbox(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ struct imapc_simple_context sctx;
+ struct imapc_command *cmd;
+
+ i_assert(root->box_name != NULL);
+
+ /* ask quotas for the configured mailbox */
+ imapc_quota_refresh_init(&root->refresh);
+ root->refresh.box_name = root->box_name;
+
+ imapc_simple_context_init(&sctx, root->client);
+ cmd = imapc_client_cmd(root->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_sendf(cmd, "GETQUOTAROOT %s", root->box_name);
+ imapc_simple_run(&sctx, &cmd);
+
+ /* if there are multiple quota roots, use the first one returned by
+ the QUOTAROOT */
+ array_sort(&root->refresh.roots, imapc_quota_refresh_root_order_cmp);
+ imapc_quota_refresh_deinit(root->root.quota, &root->refresh,
+ sctx.ret == 0);
+ if (sctx.ret < 0)
+ *error_r = t_strdup_printf(
+ "GETQUOTAROOT %s failed: %s",
+ root->box_name,
+ mail_storage_get_last_internal_error(
+ &root->client->_storage->storage, NULL));
+
+ return sctx.ret;
+}
+
+static int imapc_quota_refresh_root(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ struct imapc_simple_context sctx;
+ struct imapc_command *cmd;
+
+ i_assert(root->root_name != NULL);
+
+ /* ask quotas for the configured quota root */
+ imapc_quota_refresh_init(&root->refresh);
+
+ imapc_simple_context_init(&sctx, root->client);
+ cmd = imapc_client_cmd(root->client->client,
+ imapc_simple_callback, &sctx);
+ imapc_command_sendf(cmd, "GETQUOTA %s", root->root_name);
+ imapc_simple_run(&sctx, &cmd);
+
+ /* there shouldn't be more than one QUOTA reply, but ignore anyway
+ anything we didn't expect. */
+ while (array_count(&root->refresh.roots) > 0) {
+ const struct imapc_quota_refresh_root *refresh_root =
+ array_front(&root->refresh.roots);
+ if (strcmp(refresh_root->name, root->root_name) == 0)
+ break;
+ array_pop_front(&root->refresh.roots);
+ }
+ imapc_quota_refresh_deinit(root->root.quota, &root->refresh,
+ sctx.ret == 0);
+ if (sctx.ret < 0)
+ *error_r = t_strdup_printf(
+ "GETQUOTA %s failed: %s",
+ root->root_name,
+ mail_storage_get_last_internal_error(
+ &root->client->_storage->storage, NULL));
+ return sctx.ret;
+}
+
+static int imapc_quota_refresh(struct imapc_quota_root *root,
+ const char **error_r)
+{
+ enum imapc_capability capa;
+ int ret;
+
+ if (root->imapc_ns == NULL) {
+ /* imapc namespace is missing - disable this quota backend */
+ return 0;
+ }
+ if (root->last_refresh.tv_sec == ioloop_timeval.tv_sec &&
+ root->last_refresh.tv_usec == ioloop_timeval.tv_usec)
+ return 0;
+ if (!imapc_quota_client_init(root))
+ return 0;
+
+ if (imapc_client_get_capabilities(root->client->client, &capa) < 0) {
+ *error_r = "Failed to get server capabilities";
+ return -1;
+ }
+ if ((capa & IMAPC_CAPABILITY_QUOTA) == 0) {
+ /* no QUOTA capability - disable quota */
+ e_warning(root->root.backend.event,
+ "Remote IMAP server doesn't support QUOTA - disabling");
+ root->client = NULL;
+ return 0;
+ }
+
+ if (root->root_name == NULL)
+ ret = imapc_quota_refresh_mailbox(root, error_r);
+ else
+ ret = imapc_quota_refresh_root(root, error_r);
+
+ /* set the last_refresh only after the refresh, because it changes
+ ioloop_timeval. */
+ root->last_refresh = ioloop_timeval;
+ return ret;
+}
+
+static int imapc_quota_init_limits(struct quota_root *_root,
+ const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ return imapc_quota_refresh(root, error_r);
+}
+
+static void
+imapc_quota_namespace_added(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root **roots;
+ unsigned int i, count;
+
+ roots = array_get_modifiable(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.name == quota_backend_imapc.name &&
+ ((roots[i]->ns_prefix == NULL &&
+ ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) ||
+ roots[i]->ns == ns))
+ imapc_quota_root_namespace_added(roots[i], ns);
+ }
+}
+
+static const char *const *
+imapc_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources_both[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+ return resources_both;
+}
+
+static enum quota_get_result
+imapc_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct imapc_quota_root *root = (struct imapc_quota_root *)_root;
+
+ if (imapc_quota_refresh(root, error_r) < 0)
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *value_r = root->bytes_last;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *value_r = root->count_last;
+ else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+imapc_quota_update(struct quota_root *root ATTR_UNUSED,
+ struct quota_transaction_context *ctx ATTR_UNUSED,
+ const char **error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+struct quota_backend quota_backend_imapc = {
+ .name = "imapc",
+
+ .v = {
+ .alloc = imapc_quota_alloc,
+ .init = imapc_quota_init,
+ .deinit = imapc_quota_deinit,
+ .init_limits = imapc_quota_init_limits,
+ .namespace_added = imapc_quota_namespace_added,
+ .get_resources = imapc_quota_root_get_resources,
+ .get_resource = imapc_quota_get_resource,
+ .update = imapc_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-maildir.c b/src/plugins/quota/quota-maildir.c
new file mode 100644
index 0000000..f4fd3a7
--- /dev/null
+++ b/src/plugins/quota/quota-maildir.c
@@ -0,0 +1,953 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "nfs-workarounds.h"
+#include "safe-mkstemp.h"
+#include "mkdir-parents.h"
+#include "read-full.h"
+#include "write-full.h"
+#include "str.h"
+#include "maildir-storage.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#define MAILDIRSIZE_FILENAME "maildirsize"
+#define MAILDIRSIZE_STALE_SECS (60*15)
+
+struct maildir_quota_root {
+ struct quota_root root;
+
+ struct mail_namespace *maildirsize_ns;
+ const char *maildirsize_path;
+
+ uint64_t total_bytes;
+ uint64_t total_count;
+
+ int fd;
+ time_t recalc_last_stamp;
+ off_t last_size;
+
+ bool limits_initialized:1;
+};
+
+struct maildir_list_context {
+ struct mailbox_list *list;
+ struct maildir_quota_root *root;
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+
+ string_t *path;
+ int state;
+};
+
+extern struct quota_backend quota_backend_maildir;
+
+static struct dotlock_settings dotlock_settings = {
+ .timeout = 0,
+ .stale_timeout = 30
+};
+
+static int maildir_sum_dir(const char *dir, uint64_t *total_bytes,
+ uint64_t *total_count, const char **error_r)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ string_t *path;
+ const char *p;
+ size_t len;
+ uoff_t num;
+ int ret = 0;
+
+ dirp = opendir(dir);
+ if (dirp == NULL) {
+ if (errno == ENOENT || errno == ESTALE)
+ return 0;
+ *error_r = t_strdup_printf("opendir(%s) failed: %m", dir);
+ return -1;
+ }
+
+ path = t_str_new(256);
+ str_append(path, dir);
+ str_append_c(path, '/');
+
+ len = str_len(path);
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' &&
+ (dp->d_name[1] == '\0' || dp->d_name[1] == '.'))
+ continue;
+
+ p = strstr(dp->d_name, ",S=");
+ num = UOFF_T_MAX;
+ if (p != NULL) {
+ /* ,S=nnnn[:,] */
+ p += 3;
+ for (num = 0; *p >= '0' && *p <= '9'; p++)
+ num = num * 10 + (*p - '0');
+
+ if (*p != ':' && *p != '\0' && *p != ',') {
+ /* not in expected format, fallback to stat() */
+ num = UOFF_T_MAX;
+ } else {
+ *total_bytes += num;
+ *total_count += 1;
+ }
+ }
+ if (num == UOFF_T_MAX) {
+ struct stat st;
+
+ str_truncate(path, len);
+ str_append(path, dp->d_name);
+ if (stat(str_c(path), &st) == 0) {
+ *total_bytes += st.st_size;
+ *total_count += 1;
+ } else if (errno != ENOENT && errno != ESTALE) {
+ *error_r = t_strdup_printf(
+ "stat(%s) failed: %m", str_c(path));
+ ret = -1;
+ }
+ }
+ }
+
+ if (closedir(dirp) < 0) {
+ *error_r = t_strdup_printf("closedir(%s) failed: %m", dir);
+ return -1;
+ }
+ return ret;
+}
+
+static struct maildir_list_context *
+maildir_list_init(struct maildir_quota_root *root, struct mailbox_list *list)
+{
+ struct maildir_list_context *ctx;
+
+ ctx = i_new(struct maildir_list_context, 1);
+ ctx->root = root;
+ ctx->path = str_new(default_pool, 512);
+ ctx->list = list;
+ ctx->iter = mailbox_list_iter_init(list, "*",
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ return ctx;
+}
+
+static bool maildir_set_next_path(struct maildir_list_context *ctx)
+{
+ const char *path, *storage_name;
+
+ str_truncate(ctx->path, 0);
+
+ storage_name = mailbox_list_get_storage_name(
+ ctx->info->ns->list, ctx->info->vname);
+ if (mailbox_list_get_path(ctx->list, storage_name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) > 0) {
+ str_append(ctx->path, path);
+ str_append(ctx->path, ctx->state == 0 ?
+ "/new" : "/cur");
+ }
+
+ return str_len(ctx->path) > 0;
+}
+
+static const char *
+maildir_list_next(struct maildir_list_context *ctx, time_t *mtime_r)
+{
+ struct quota_rule *rule;
+ struct stat st;
+
+ for (;;) {
+ if (ctx->state == 0) {
+ ctx->info = mailbox_list_iter_next(ctx->iter);
+ if (ctx->info == NULL)
+ return NULL;
+
+ rule = quota_root_rule_find(ctx->root->root.set,
+ ctx->info->vname);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ continue;
+ }
+ }
+
+ if (!maildir_set_next_path(ctx)) {
+ ctx->state = 0;
+ continue;
+ }
+
+ if (++ctx->state == 2)
+ ctx->state = 0;
+
+ if (stat(str_c(ctx->path), &st) == 0)
+ break;
+ /* ignore if the directory got lost, stale or if it was
+ actually a file and not a directory */
+ if (errno != ENOENT && errno != ESTALE && errno != ENOTDIR) {
+ e_error(ctx->root->root.backend.event,
+ "stat(%s) failed: %m", str_c(ctx->path));
+ ctx->state = 0;
+ }
+ }
+
+ *mtime_r = st.st_mtime;
+ return str_c(ctx->path);
+}
+
+static int maildir_list_deinit(struct maildir_list_context *ctx,
+ const char **error_r)
+{
+ int ret = mailbox_list_iter_deinit(&ctx->iter);
+ if (ret < 0)
+ *error_r = t_strdup_printf(
+ "Listing mailboxes failed: %s",
+ mailbox_list_get_last_internal_error(ctx->list, NULL));
+
+ str_free(&ctx->path);
+ i_free(ctx);
+ return ret;
+}
+
+static int
+maildirs_check_have_changed(struct maildir_quota_root *root,
+ struct mail_namespace *ns, time_t latest_mtime,
+ const char **error_r)
+{
+ struct maildir_list_context *ctx;
+ time_t mtime;
+ int ret = 0;
+
+ ctx = maildir_list_init(root, ns->list);
+ while (maildir_list_next(ctx, &mtime) != NULL) {
+ if (mtime > latest_mtime) {
+ ret = 1;
+ break;
+ }
+ }
+ if (maildir_list_deinit(ctx, error_r) < 0)
+ return -1;
+ return ret;
+}
+
+static int maildirsize_write(struct maildir_quota_root *root, const char *path)
+{
+ const struct mail_storage_settings *set =
+ root->maildirsize_ns->mail_set;
+ struct quota_root *_root = &root->root;
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+ struct mailbox_permissions perm;
+ const char *p, *dir;
+ string_t *str, *temp_path;
+ int fd;
+
+ i_assert(root->fd == -1);
+
+ /* figure out what permissions we should use for maildirsize.
+ use the inbox namespace's permissions if possible. */
+ perm.file_create_mode = 0600; perm.dir_create_mode = 0700;
+ perm.file_create_gid = (gid_t)-1;
+ perm.file_create_gid_origin = "default";
+ namespaces = array_get(&root->root.quota->namespaces, &count);
+ i_assert(count > 0);
+ for (i = 0; i < count; i++) {
+ if ((namespaces[i]->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
+ continue;
+
+ mailbox_list_get_root_permissions(namespaces[i]->list,
+ &perm);
+ break;
+ }
+
+ dotlock_settings.use_excl_lock = set->dotlock_use_excl;
+ dotlock_settings.nfs_flush = set->mail_nfs_storage;
+
+ temp_path = t_str_new(128);
+ str_append(temp_path, path);
+ fd = safe_mkstemp_hostpid_group(temp_path, perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ if (fd == -1 && errno == ENOENT) {
+ /* the control directory doesn't exist yet? create it */
+ p = strrchr(path, '/');
+ dir = t_strdup_until(path, p);
+ if (mkdir_parents_chgrp(dir, perm.dir_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ e_error(root->root.backend.event,
+ "mkdir_parents(%s) failed: %m", dir);
+ return -1;
+ }
+ fd = safe_mkstemp_hostpid_group(temp_path,
+ perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ }
+ if (fd == -1) {
+ e_error(root->root.backend.event,
+ "safe_mkstemp(%s) failed: %m", path);
+ return -1;
+ }
+
+ str = t_str_new(128);
+ /* if we have no limits, write 0S instead of an empty line */
+ if (_root->bytes_limit != 0 || _root->count_limit == 0) {
+ str_printfa(str, "%"PRId64"S", _root->bytes_limit);
+ }
+ if (_root->count_limit != 0) {
+ if (str_len(str) > 0)
+ str_append_c(str, ',');
+ str_printfa(str, "%"PRIu64"C", _root->count_limit);
+ }
+ str_printfa(str, "\n%"PRIu64" %"PRIu64"\n",
+ root->total_bytes, root->total_count);
+ if (write_full(fd, str_data(str), str_len(str)) < 0) {
+ e_error(root->root.backend.event,
+ "write_full(%s) failed: %m", str_c(temp_path));
+ i_close_fd(&fd);
+ i_unlink(str_c(temp_path));
+ return -1;
+ }
+ i_close_fd(&fd);
+
+ if (rename(str_c(temp_path), path) < 0) {
+ e_error(root->root.backend.event,
+ "rename(%s, %s) failed: %m", str_c(temp_path), path);
+ i_unlink_if_exists(str_c(temp_path));
+ return -1;
+ }
+ return 0;
+}
+
+static void maildirsize_recalculate_init(struct maildir_quota_root *root)
+{
+ root->total_bytes = root->total_count = 0;
+ root->recalc_last_stamp = 0;
+}
+
+static int maildirsize_recalculate_namespace(struct maildir_quota_root *root,
+ struct mail_namespace *ns,
+ const char **error_r)
+{
+ struct maildir_list_context *ctx;
+ const char *dir;
+ time_t mtime;
+ int ret = 0;
+
+ ctx = maildir_list_init(root, ns->list);
+ while ((dir = maildir_list_next(ctx, &mtime)) != NULL) {
+ if (mtime > root->recalc_last_stamp)
+ root->recalc_last_stamp = mtime;
+
+ if (maildir_sum_dir(dir, &root->total_bytes,
+ &root->total_count, error_r) < 0)
+ ret = -1;
+ }
+ if (maildir_list_deinit(ctx, error_r) < 0)
+ ret = -1;
+
+ return ret;
+}
+
+static void maildirsize_rebuild_later(struct maildir_quota_root *root)
+{
+ if (!root->root.set->force_default_rule) {
+ /* FIXME: can't unlink(), because the limits would be lost. */
+ return;
+ }
+
+ if (unlink(root->maildirsize_path) < 0 &&
+ errno != ENOENT && errno != ESTALE)
+ e_error(root->root.backend.event,
+ "unlink(%s) failed: %m", root->maildirsize_path);
+}
+
+static int maildirsize_recalculate_finish(struct maildir_quota_root *root,
+ int ret, const char **error_r)
+{
+ if (ret == 0) {
+ /* maildir didn't change, we can write the maildirsize file */
+ if ((ret = maildirsize_write(root, root->maildirsize_path)) < 0)
+ *error_r = "failed to write maildirsize";
+ }
+ if (ret != 0)
+ maildirsize_rebuild_later(root);
+
+ return ret;
+}
+
+static int maildirsize_recalculate(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ struct mail_namespace *const *namespaces;
+ struct event_reason *reason;
+ unsigned int i, count;
+ int ret = 0;
+
+ reason = event_reason_begin("quota:recalculate");
+ maildirsize_recalculate_init(root);
+
+ /* count mails from all namespaces */
+ namespaces = array_get(&root->root.quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(&root->root, namespaces[i]))
+ continue;
+
+ if (maildirsize_recalculate_namespace(root, namespaces[i], error_r) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+
+ if (ret == 0) {
+ /* check if any of the directories have changed */
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_namespace_visible(&root->root,
+ namespaces[i]))
+ continue;
+
+ ret = maildirs_check_have_changed(root, namespaces[i],
+ root->recalc_last_stamp,
+ error_r);
+ if (ret != 0)
+ break;
+ }
+ }
+
+ ret = maildirsize_recalculate_finish(root, ret, error_r);
+ event_reason_end(&reason);
+ return ret;
+}
+
+static bool
+maildir_parse_limit(const char *str, uint64_t *bytes_r, uint64_t *count_r)
+{
+ const char *const *limit;
+ unsigned long long value;
+ const char *pos;
+ bool ret = TRUE;
+
+ *bytes_r = 0;
+ *count_r = 0;
+
+ /* 0 values mean unlimited */
+ for (limit = t_strsplit(str, ","); *limit != NULL; limit++) {
+ if (str_parse_ullong(*limit, &value, &pos) < 0) {
+ ret = FALSE;
+ continue;
+ }
+ if (pos[0] != '\0' && pos[1] == '\0') {
+ switch (pos[0]) {
+ case 'C':
+ if (value != 0)
+ *count_r = value;
+ break;
+ case 'S':
+ if (value != 0)
+ *bytes_r = value;
+ break;
+ default:
+ ret = FALSE;
+ break;
+ }
+ } else {
+ ret = FALSE;
+ }
+ }
+ return ret;
+}
+
+static int maildirsize_parse(struct maildir_quota_root *root,
+ int fd, const char *const *lines)
+{
+ struct quota_root *_root = &root->root;
+ uint64_t message_bytes_limit, message_count_limit;
+ long long bytes_diff, total_bytes;
+ int count_diff, total_count;
+ unsigned int line_count = 0;
+
+ if (*lines == NULL)
+ return -1;
+
+ /* first line contains the limits */
+ (void)maildir_parse_limit(lines[0], &message_bytes_limit,
+ &message_count_limit);
+
+ /* truncate too high limits to signed 64bit int range */
+ if (message_bytes_limit >= (1ULL << 63))
+ message_bytes_limit = (1ULL << 63) - 1;
+ if (message_count_limit >= (1ULL << 63))
+ message_count_limit = (1ULL << 63) - 1;
+
+ if (root->root.bytes_limit == (int64_t)message_bytes_limit &&
+ root->root.count_limit == (int64_t)message_count_limit) {
+ /* limits haven't changed */
+ } else if (root->root.set->force_default_rule) {
+ /* we know the limits and they've changed.
+ the file must be rewritten. */
+ return 0;
+ } else {
+ /* we're using limits from the file. */
+ root->root.bytes_limit = message_bytes_limit;
+ root->root.count_limit = message_count_limit;
+ quota_root_recalculate_relative_rules(root->root.set,
+ message_bytes_limit,
+ message_count_limit);
+ }
+
+ if (*lines == NULL) {
+ /* no quota lines. rebuild it. */
+ return 0;
+ }
+
+ /* rest of the lines contains <bytes> <count> diffs */
+ total_bytes = 0; total_count = 0;
+ for (lines++; *lines != NULL; lines++, line_count++) {
+ if (sscanf(*lines, "%lld %d", &bytes_diff, &count_diff) != 2)
+ return -1;
+
+ total_bytes += bytes_diff;
+ total_count += count_diff;
+ }
+
+ if (total_bytes < 0 || total_count < 0) {
+ /* corrupted */
+ return -1;
+ }
+
+ if ((total_bytes > _root->bytes_limit && _root->bytes_limit != 0) ||
+ (total_count > _root->count_limit && _root->count_limit != 0)) {
+ /* we're over quota. don't trust these values if the file
+ contains more than the initial summary line, or if the file
+ is older than 15 minutes. */
+ struct stat st;
+
+ if (line_count > 1)
+ return 0;
+
+ if (fstat(fd, &st) < 0 ||
+ st.st_mtime < ioloop_time - MAILDIRSIZE_STALE_SECS)
+ return 0;
+ }
+ root->total_bytes = (uint64_t)total_bytes;
+ root->total_count = (uint64_t)total_count;
+ return 1;
+}
+
+static int maildirsize_open(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ i_close_fd_path(&root->fd, root->maildirsize_path);
+
+ root->fd = nfs_safe_open(root->maildirsize_path, O_RDWR | O_APPEND);
+ if (root->fd == -1) {
+ if (errno == ENOENT)
+ return 0;
+ *error_r = t_strdup_printf(
+ "open(%s) failed: %m", root->maildirsize_path);
+ return -1;
+ }
+ return 1;
+}
+
+static bool maildirsize_has_changed(struct maildir_quota_root *root)
+{
+ struct stat st1, st2;
+
+ if (dotlock_settings.nfs_flush) {
+ nfs_flush_file_handle_cache(root->maildirsize_path);
+ nfs_flush_attr_cache_unlocked(root->maildirsize_path);
+ }
+
+ if (root->fd == -1)
+ return TRUE;
+
+ if (stat(root->maildirsize_path, &st1) < 0)
+ return TRUE;
+ if (fstat(root->fd, &st2) < 0)
+ return TRUE;
+
+ return root->last_size != st2.st_size || st1.st_ino != st2.st_ino ||
+ !CMP_DEV_T(st1.st_dev, st2.st_dev);
+}
+
+static int maildirsize_read(struct maildir_quota_root *root, bool *retry,
+ const char **error_r)
+{
+ char buf[5120+1];
+ unsigned int i, size;
+ bool retry_estale = *retry;
+ int ret;
+
+ *retry = FALSE;
+
+ if (!maildirsize_has_changed(root))
+ return 1;
+
+ if ((ret = maildirsize_open(root, error_r)) <= 0)
+ return ret;
+
+ /* @UNSAFE */
+ size = 0;
+ while ((ret = read(root->fd, buf + size, sizeof(buf)-1 - size)) != 0) {
+ if (ret < 0) {
+ if (errno == ESTALE && retry_estale) {
+ *retry = TRUE;
+ break;
+ }
+ *error_r = t_strdup_printf(
+ "read(%s) failed: %m", root->maildirsize_path);
+ break;
+ }
+ size += ret;
+ if (size >= sizeof(buf)-1) {
+ /* we'll need to recalculate the quota */
+ break;
+ }
+ }
+
+ /* try to use the file even if we ran into some error. if we don't have
+ forced limits, we'll need to read the header to get them */
+ root->total_bytes = root->total_count = 0;
+ root->last_size = size;
+
+ /* skip the last line if there's no LF at the end. Remove the last LF
+ so we don't get one empty line in the strsplit. */
+ while (size > 0 && buf[size-1] != '\n') size--;
+ if (size > 0) size--;
+ buf[size] = '\0';
+
+ if (ret < 0 && size == 0) {
+ /* the read failed and there's no usable header, fail. */
+ i_close_fd(&root->fd);
+ return -1;
+ }
+
+ /* If there are any NUL bytes, the file is broken. */
+ for (i = 0; i < size; i++) {
+ if (buf[i] == '\0')
+ break;
+ }
+
+ if (i == size &&
+ maildirsize_parse(root, root->fd, t_strsplit(buf, "\n")) > 0 &&
+ ret == 0)
+ ret = 1;
+ else {
+ /* broken file / need recalculation */
+ i_close_fd(&root->fd);
+ ret = 0;
+ }
+ return ret;
+}
+
+static bool maildirquota_limits_init(struct maildir_quota_root *root)
+{
+ struct mailbox_list *list;
+ struct mail_storage *storage;
+ const char *control_dir;
+
+ if (root->limits_initialized)
+ return root->maildirsize_path != NULL;
+ root->limits_initialized = TRUE;
+
+ if (root->maildirsize_ns == NULL) {
+ i_assert(root->maildirsize_path == NULL);
+ return FALSE;
+ }
+
+ list = root->maildirsize_ns->list;
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ strcmp(storage->name, MAILDIR_STORAGE_NAME) != 0) {
+ /* non-maildir namespace, skip */
+ if ((storage->class_flags &
+ MAIL_STORAGE_CLASS_FLAG_NOQUOTA) == 0) {
+ e_warning(root->root.backend.event,
+ "Namespace '%s' is not Maildir, "
+ "skipping for Maildir++ quota",
+ root->maildirsize_ns->prefix);
+ }
+ root->maildirsize_path = NULL;
+ return FALSE;
+ }
+ if (root->maildirsize_path == NULL) {
+ if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_CONTROL,
+ &control_dir))
+ i_unreached();
+ root->maildirsize_path =
+ p_strconcat(root->root.pool, control_dir,
+ "/"MAILDIRSIZE_FILENAME, NULL);
+ }
+ return TRUE;
+}
+
+static int maildirquota_read_limits(struct maildir_quota_root *root,
+ const char **error_r)
+{
+ bool retry = TRUE;
+ int ret, n = 0;
+
+ if (!maildirquota_limits_init(root))
+ return 1;
+
+ do {
+ if (n == NFS_ESTALE_RETRY_COUNT)
+ retry = FALSE;
+ ret = maildirsize_read(root, &retry, error_r);
+ n++;
+ } while (ret == -1 && retry);
+ return ret;
+}
+
+static int
+maildirquota_refresh(struct maildir_quota_root *root, bool *recalculated_r,
+ const char **error_r)
+{
+ int ret;
+
+ *recalculated_r = FALSE;
+
+ ret = maildirquota_read_limits(root, error_r);
+ if (ret == 0) {
+ if (root->root.bytes_limit == 0 &&
+ root->root.count_limit == 0 &&
+ root->root.set->default_rule.bytes_limit == 0 &&
+ root->root.set->default_rule.count_limit == 0) {
+ /* no quota */
+ if (!root->root.set->force_default_rule)
+ return 0;
+ /* explicitly specified 0 as quota. keep the quota
+ updated even if it's not enforced. */
+ }
+
+ ret = maildirsize_recalculate(root, error_r);
+ if (ret == 0)
+ *recalculated_r = TRUE;
+ }
+ return ret < 0 ? -1 : 0;
+}
+
+static int maildirsize_update(struct maildir_quota_root *root,
+ int count_diff, int64_t bytes_diff)
+{
+ char str[MAX_INT_STRLEN * 2 + 2];
+ int ret = 0;
+
+ if (count_diff == 0 && bytes_diff == 0)
+ return 0;
+
+ /* We rely on O_APPEND working in here. That isn't NFS-safe, but it
+ isn't necessarily that bad because the file is recreated once in
+ a while, and sooner if corruption causes calculations to go
+ over quota. This is also how Maildir++ spec specifies it should be
+ done.. */
+ if (i_snprintf(str, sizeof(str), "%lld %d\n",
+ (long long)bytes_diff, count_diff) < 0)
+ i_unreached();
+ if (write_full(root->fd, str, strlen(str)) < 0) {
+ ret = -1;
+ if (errno == ESTALE) {
+ /* deleted/replaced already, ignore */
+ } else {
+ e_error(root->root.backend.event,
+ "write_full(%s) failed: %m",
+ root->maildirsize_path);
+ }
+ } else {
+ /* close the file to force a flush with NFS */
+ if (close(root->fd) < 0) {
+ ret = -1;
+ if (errno != ESTALE)
+ e_error(root->root.backend.event,
+ "close(%s) failed: %m", root->maildirsize_path);
+ }
+ root->fd = -1;
+ }
+ return ret;
+}
+
+static struct quota_root *maildir_quota_alloc(void)
+{
+ struct maildir_quota_root *root;
+
+ root = i_new(struct maildir_quota_root, 1);
+ root->fd = -1;
+ return &root->root;
+}
+
+static int maildir_quota_init(struct quota_root *_root, const char *args,
+ const char **error_r)
+{
+ event_set_append_log_prefix(_root->backend.event, "quota-maildir: ");
+ return quota_root_default_init(_root, args, error_r);
+}
+
+static void maildir_quota_deinit(struct quota_root *_root)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+ i_close_fd(&root->fd);
+ i_free(root);
+}
+
+static bool
+maildir_quota_parse_rule(struct quota_root_settings *root_set ATTR_UNUSED,
+ struct quota_rule *rule,
+ const char *str, const char **error_r)
+{
+ uint64_t bytes, count;
+
+ if (strcmp(str, "NOQUOTA") == 0) {
+ bytes = 0;
+ count = 0;
+ } else if (!maildir_parse_limit(str, &bytes, &count)) {
+ *error_r = t_strdup_printf(
+ "quota-maildir: Invalid Maildir++ quota rule \"%s\"",
+ str);
+ return FALSE;
+ }
+
+ rule->bytes_limit = bytes;
+ rule->count_limit = count;
+ return TRUE;
+}
+
+static int maildir_quota_init_limits(struct quota_root *_root,
+ const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ const char *error;
+
+ if (maildirquota_read_limits(root, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "quota-maildir: Failed to read limits: %s", error);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+maildir_quota_root_namespace_added(struct quota_root *_root,
+ struct mail_namespace *ns)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+
+ if (root->maildirsize_ns == NULL)
+ root->maildirsize_ns = ns;
+}
+
+static void
+maildir_quota_namespace_added(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root **roots;
+ unsigned int i, count;
+
+ roots = array_get_modifiable(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.name == quota_backend_maildir.name &&
+ ((roots[i]->ns_prefix == NULL &&
+ ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) ||
+ roots[i]->ns == ns))
+ maildir_quota_root_namespace_added(roots[i], ns);
+ }
+}
+
+static const char *const *
+maildir_quota_root_get_resources(struct quota_root *root ATTR_UNUSED)
+{
+ static const char *resources_both[] = {
+ QUOTA_NAME_STORAGE_KILOBYTES,
+ QUOTA_NAME_MESSAGES,
+ NULL
+ };
+
+ return resources_both;
+}
+
+static enum quota_get_result
+maildir_quota_get_resource(struct quota_root *_root, const char *name,
+ uint64_t *value_r, const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ bool recalculated;
+ const char *error;
+
+ if (maildirquota_refresh(root, &recalculated, &error) < 0) {
+ *error_r = t_strdup_printf("Failed to get %s: %s", name, error);
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) {
+ *value_r = root->total_bytes;
+ } else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0) {
+ *value_r = root->total_count;
+ } else {
+ *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING;
+ return QUOTA_GET_RESULT_UNKNOWN_RESOURCE;
+ }
+ return QUOTA_GET_RESULT_LIMITED;
+}
+
+static int
+maildir_quota_update(struct quota_root *_root,
+ struct quota_transaction_context *ctx,
+ const char **error_r)
+{
+ struct maildir_quota_root *root = (struct maildir_quota_root *)_root;
+ bool recalculated;
+ const char *error;
+
+ if (!maildirquota_limits_init(root)) {
+ /* no limits */
+ return 0;
+ }
+
+ /* even though we don't really care about the limits in here ourself,
+ we do want to make sure the header gets updated if the limits have
+ changed. also this makes sure the maildirsize file is created if
+ it doesn't exist. */
+ if (maildirquota_refresh(root, &recalculated, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Could not update storage usage data: %s",
+ error);
+ return -1;
+ }
+
+ if (recalculated) {
+ /* quota was just recalculated and it already contains the changes
+ we wanted to do. */
+ } else if (root->fd == -1) {
+ if (maildirsize_recalculate(root, &error) < 0)
+ e_error(root->root.backend.event, "%s", error);
+ } else if (ctx->recalculate != QUOTA_RECALCULATE_DONT) {
+ i_close_fd(&root->fd);
+ if (maildirsize_recalculate(root, &error) < 0)
+ e_error(root->root.backend.event, "%s", error);
+ } else if (maildirsize_update(root, ctx->count_used, ctx->bytes_used) < 0) {
+ i_close_fd(&root->fd);
+ maildirsize_rebuild_later(root);
+ }
+
+ return 0;
+}
+
+struct quota_backend quota_backend_maildir = {
+ .name = "maildir",
+
+ .v = {
+ .alloc = maildir_quota_alloc,
+ .init = maildir_quota_init,
+ .deinit = maildir_quota_deinit,
+ .parse_rule = maildir_quota_parse_rule,
+ .init_limits = maildir_quota_init_limits,
+ .namespace_added = maildir_quota_namespace_added,
+ .get_resources = maildir_quota_root_get_resources,
+ .get_resource = maildir_quota_get_resource,
+ .update = maildir_quota_update,
+ }
+};
diff --git a/src/plugins/quota/quota-plugin.c b/src/plugins/quota/quota-plugin.c
new file mode 100644
index 0000000..507a329
--- /dev/null
+++ b/src/plugins/quota/quota-plugin.c
@@ -0,0 +1,31 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-user.h"
+#include "mail-storage-hooks.h"
+#include "quota-plugin.h"
+
+void quota_backends_register(void);
+void quota_backends_unregister(void);
+
+const char *quota_plugin_version = DOVECOT_ABI_VERSION;
+
+static struct mail_storage_hooks quota_mail_storage_hooks = {
+ .mail_user_created = quota_mail_user_created,
+ .mail_namespaces_created = quota_mail_namespaces_created,
+ .mailbox_list_created = quota_mailbox_list_created,
+ .mailbox_allocated = quota_mailbox_allocated,
+ .mail_allocated = quota_mail_allocated
+};
+
+void quota_plugin_init(struct module *module)
+{
+ mail_storage_hooks_add(module, &quota_mail_storage_hooks);
+ quota_backends_register();
+}
+
+void quota_plugin_deinit(void)
+{
+ mail_storage_hooks_remove(&quota_mail_storage_hooks);
+ quota_backends_unregister();
+}
diff --git a/src/plugins/quota/quota-plugin.h b/src/plugins/quota/quota-plugin.h
new file mode 100644
index 0000000..927d428
--- /dev/null
+++ b/src/plugins/quota/quota-plugin.h
@@ -0,0 +1,36 @@
+#ifndef QUOTA_PLUGIN_H
+#define QUOTA_PLUGIN_H
+
+#include "module-context.h"
+#include "mail-user.h"
+
+struct module;
+struct mailbox;
+struct mailbox_list;
+struct mail;
+
+#define QUOTA_USER_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_user_module)
+#define QUOTA_USER_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_user_module)
+
+struct quota_user {
+ union mail_user_module_context module_ctx;
+
+ struct quota *quota;
+};
+
+struct mail_storage;
+
+extern MODULE_CONTEXT_DEFINE(quota_user_module, &mail_user_module_register);
+
+void quota_mail_user_created(struct mail_user *user);
+void quota_mailbox_list_created(struct mailbox_list *list);
+void quota_mail_namespaces_created(struct mail_namespace *namespaces);
+void quota_mailbox_allocated(struct mailbox *box);
+void quota_mail_allocated(struct mail *mail);
+
+void quota_plugin_init(struct module *module);
+void quota_plugin_deinit(void);
+
+#endif
diff --git a/src/plugins/quota/quota-private.h b/src/plugins/quota/quota-private.h
new file mode 100644
index 0000000..ac091d3
--- /dev/null
+++ b/src/plugins/quota/quota-private.h
@@ -0,0 +1,230 @@
+#ifndef QUOTA_PRIVATE_H
+#define QUOTA_PRIVATE_H
+
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "quota.h"
+
+/* Modules should use do "my_id = quota_module_id++" and
+ use quota_module_contexts[id] for their own purposes. */
+extern unsigned int quota_module_id;
+
+struct quota {
+ struct mail_user *user;
+ struct quota_settings *set;
+ struct event *event;
+
+ ARRAY(struct quota_root *) roots;
+ ARRAY(struct mail_namespace *) namespaces;
+ struct mail_namespace *unwanted_ns;
+};
+
+struct quota_settings {
+ pool_t pool;
+
+ ARRAY(struct quota_root_settings *) root_sets;
+ struct event *event;
+ enum quota_alloc_result (*test_alloc)(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
+
+ uoff_t max_mail_size;
+ const char *quota_exceeded_msg;
+ bool debug:1;
+ bool initialized:1;
+ bool vsizes:1;
+};
+
+struct quota_rule {
+ const char *mailbox_mask;
+
+ int64_t bytes_limit, count_limit;
+ /* relative to default_rule */
+ int bytes_percent, count_percent;
+
+ /* Don't include this mailbox in quota */
+ bool ignore:1;
+};
+
+struct quota_warning_rule {
+ struct quota_rule rule;
+ const char *command;
+ bool reverse:1;
+};
+
+struct quota_backend_vfuncs {
+ struct quota_root *(*alloc)(void);
+ int (*init)(struct quota_root *root, const char *args,
+ const char **error_r);
+ void (*deinit)(struct quota_root *root);
+
+ bool (*parse_rule)(struct quota_root_settings *root_set,
+ struct quota_rule *rule,
+ const char *str, const char **error_r);
+ int (*init_limits)(struct quota_root *root, const char **error_r);
+
+ /* called once for each namespace */
+ void (*namespace_added)(struct quota *quota,
+ struct mail_namespace *ns);
+
+ const char *const *(*get_resources)(struct quota_root *root);
+ /* Backends return success as QUOTA_GET_RESULT_LIMITED, and returning
+ QUOTA_GET_RESULT_UNLIMITED is prohibited by quota_get_resource(),
+ which is the only caller of this vfunc. */
+ enum quota_get_result (*get_resource)(struct quota_root *root,
+ const char *name,
+ uint64_t *value_r,
+ const char **error_r);
+
+ int (*update)(struct quota_root *root,
+ struct quota_transaction_context *ctx,
+ const char **error_r);
+ bool (*match_box)(struct quota_root *root, struct mailbox *box);
+ void (*flush)(struct quota_root *root);
+};
+
+struct quota_backend {
+ /* quota backends equal if backend1.name == backend2.name */
+ const char *name;
+ struct event *event;
+ struct quota_backend_vfuncs v;
+};
+
+struct quota_root_settings {
+ /* Unique quota root name. */
+ const char *name;
+ /* Name in settings, e.g. "quota", "quota2", .. */
+ const char *set_name;
+
+ struct quota_settings *set;
+ const char *args;
+
+ const struct quota_backend *backend;
+ struct quota_rule default_rule;
+ ARRAY(struct quota_rule) rules;
+ ARRAY(struct quota_warning_rule) warning_rules;
+ const char *limit_set;
+
+ /* If user is under quota before saving a mail, allow the last mail to
+ bring the user over quota by this many bytes. */
+ uint64_t last_mail_max_extra_bytes;
+ struct quota_rule grace_rule;
+
+ /* Limits in default_rule override backend's quota limits */
+ bool force_default_rule:1;
+ /* TRUE if any of the warning_rules have reverse==TRUE */
+ bool have_reverse_warnings:1;
+};
+
+struct quota_root {
+ pool_t pool;
+
+ struct quota_root_settings *set;
+ struct quota *quota;
+ struct quota_backend backend;
+ struct dict *limit_set_dict;
+
+ /* this quota root applies only to this namespace. it may also be
+ a public namespace without an owner. */
+ struct mail_namespace *ns;
+ /* this is set in quota init(), because namespaces aren't known yet.
+ when accessing shared users the ns_prefix may be non-NULL but
+ ns=NULL, so when checking if quota root applies only to a specific
+ namespace use the ns_prefix!=NULL check. */
+ const char *ns_prefix;
+
+ /* initially the same as set->default_rule.*_limit, but some backends
+ may change these by reading the limits elsewhere (e.g. Maildir++,
+ FS quota) */
+ int64_t bytes_limit, count_limit;
+
+ /* Module-specific contexts. See quota_module_id. */
+ ARRAY(void) quota_module_contexts;
+
+ /* don't enforce quota when saving */
+ bool no_enforcing:1;
+ /* quota is automatically updated. update() should be called but the
+ bytes won't be changed. count is still changed, because it's cheap
+ to do and it's internally used to figure out whether there have
+ been some changes and that quota_warnings should be checked. */
+ bool auto_updating:1;
+ /* If user has unlimited quota, disable quota tracking */
+ bool disable_unlimited_tracking:1;
+ /* Set while quota is being recalculated to avoid recursion. */
+ bool recounting:1;
+ /* Quota root is hidden (to e.g. IMAP GETQUOTAROOT) */
+ bool hidden:1;
+ /* Did we already check quota_over_flag correctness? */
+ bool quota_over_flag_checked:1;
+};
+
+struct quota_transaction_context {
+ union mailbox_transaction_module_context module_ctx;
+
+ struct quota *quota;
+ struct mailbox *box;
+
+ int64_t bytes_used, count_used;
+ /* how many bytes/mails can be saved until limit is reached.
+ (set once, not updated by bytes_used/count_used).
+
+ if last_mail_max_extra_bytes>0, the bytes_ceil is initially
+ increased by that much, while bytes_ceil2 contains the real ceiling.
+ after the first allocation is done, bytes_ceil is set to
+ bytes_ceil2. */
+ uint64_t bytes_ceil, bytes_ceil2, count_ceil;
+ /* How many bytes/mails we are over quota. Like *_ceil, these are set
+ only once and not updated by bytes_used/count_used. (Either *_ceil
+ or *_over is always zero.) */
+ uint64_t bytes_over, count_over;
+
+ struct mail *tmp_mail;
+ enum quota_recalculate recalculate;
+
+ bool limits_set:1;
+ bool failed:1;
+ bool sync_transaction:1;
+ /* TRUE if all roots have auto_updating=TRUE */
+ bool auto_updating:1;
+ /* Quota doesn't need to be updated within this transaction. */
+ bool no_quota_updates:1;
+};
+
+/* Register storage to all user's quota roots. */
+void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns);
+void quota_remove_user_namespace(struct mail_namespace *ns);
+
+int quota_root_default_init(struct quota_root *root, const char *args,
+ const char **error_r);
+struct quota *quota_get_mail_user_quota(struct mail_user *user);
+
+bool quota_root_is_namespace_visible(struct quota_root *root,
+ struct mail_namespace *ns);
+struct quota_rule *
+quota_root_rule_find(struct quota_root_settings *root_set, const char *name);
+
+void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set,
+ int64_t bytes_limit,
+ int64_t count_limit);
+/* Returns 1 if values were returned successfully, 0 if we're recursing into
+ the same function, -1 if error. */
+int quota_count(struct quota_root *root, uint64_t *bytes_r, uint64_t *count_r,
+ enum quota_get_result *error_result_r, const char **error_r);
+
+int quota_root_parse_grace(struct quota_root_settings *root_set,
+ const char *value, const char **error_r);
+bool quota_warning_match(const struct quota_warning_rule *w,
+ uint64_t bytes_before, uint64_t bytes_current,
+ uint64_t count_before, uint64_t count_current,
+ const char **reason_r);
+bool quota_transaction_is_over(struct quota_transaction_context *ctx, uoff_t size);
+int quota_transaction_set_limits(struct quota_transaction_context *ctx,
+ enum quota_get_result *error_result_r,
+ const char **error_r);
+
+void quota_backend_register(const struct quota_backend *backend);
+void quota_backend_unregister(const struct quota_backend *backend);
+
+#define QUOTA_UNKNOWN_RESOURCE_ERROR_STRING "Unknown quota resource"
+
+#endif
diff --git a/src/plugins/quota/quota-status-settings.c b/src/plugins/quota/quota-status-settings.c
new file mode 100644
index 0000000..e98cc09
--- /dev/null
+++ b/src/plugins/quota/quota-status-settings.c
@@ -0,0 +1,37 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "quota-status-settings.h"
+
+#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 = &quota_status_default_settings,
+
+ .type_offset = SIZE_MAX,
+ .struct_size = sizeof(struct quota_status_settings),
+
+ .parent_offset = SIZE_MAX,
+ .dependencies = quota_status_setting_dependencies
+};
diff --git a/src/plugins/quota/quota-status-settings.h b/src/plugins/quota/quota-status-settings.h
new file mode 100644
index 0000000..e69a0aa
--- /dev/null
+++ b/src/plugins/quota/quota-status-settings.h
@@ -0,0 +1,10 @@
+#ifndef QUOTA_STATUS_SETTINGS_H
+#define QUOTA_STATUS_SETTINGS_H 1
+
+struct quota_status_settings {
+ const char *recipient_delimiter;
+};
+
+extern const struct setting_parser_info quota_status_setting_parser_info;
+
+#endif
diff --git a/src/plugins/quota/quota-status.c b/src/plugins/quota/quota-status.c
new file mode 100644
index 0000000..7db0cb4
--- /dev/null
+++ b/src/plugins/quota/quota-status.c
@@ -0,0 +1,360 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "connection.h"
+#include "restrict-access.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mail-storage-settings.h"
+#include "mail-storage-service.h"
+#include "smtp-address.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+#include "quota-status-settings.h"
+
+enum quota_protocol {
+ QUOTA_PROTOCOL_UNKNOWN = 0,
+ QUOTA_PROTOCOL_POSTFIX
+};
+
+struct quota_client {
+ struct connection conn;
+
+ struct event *event;
+
+ char *state;
+ char *recipient;
+ uoff_t size;
+
+ bool warned_bad_state:1;
+};
+
+static struct event_category event_category_quota_status = {
+ .name = "quota-status"
+};
+
+static struct quota_status_settings *quota_status_settings;
+static pool_t quota_status_pool;
+static enum quota_protocol protocol;
+static struct mail_storage_service_ctx *storage_service;
+static struct connection_list *clients;
+static char *nouser_reply;
+
+static void client_connected(struct master_service_connection *conn)
+{
+ struct quota_client *client;
+
+ client = i_new(struct quota_client, 1);
+
+ client->event = event_create(NULL);
+ client->conn.event_parent = client->event;
+ event_add_category(client->event, &event_category_quota_status);
+ connection_init_server(clients, &client->conn,
+ "quota-client", conn->fd, conn->fd);
+ master_service_client_connection_accept(conn);
+
+ e_debug(client->event, "Client connected");
+}
+
+static void client_reset(struct quota_client *client)
+{
+ i_free(client->state);
+ i_free(client->recipient);
+}
+
+static enum quota_alloc_result
+quota_check(struct mail_user *user, uoff_t mail_size, const char **error_r)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct quota_transaction_context *ctx;
+ enum quota_alloc_result ret;
+
+ if (quser == NULL) {
+ /* no quota for user */
+ e_debug(user->event, "User has no quota");
+ return QUOTA_ALLOC_RESULT_OK;
+ }
+
+ ns = mail_namespace_find_inbox(user->namespaces);
+ box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_POST_SESSION);
+
+ ctx = quota_transaction_begin(box);
+ const char *internal_error;
+ ret = quota_test_alloc(ctx, I_MAX(1, mail_size), &internal_error);
+ if (ret == QUOTA_ALLOC_RESULT_TEMPFAIL)
+ e_error(user->event, "quota check failed: %s", internal_error);
+ *error_r = quota_alloc_result_errstr(ret, ctx);
+ quota_transaction_rollback(&ctx);
+
+ mailbox_free(&box);
+ return ret;
+}
+
+static int client_check_mta_state(struct quota_client *client)
+{
+ if (client->state == NULL || strcasecmp(client->state, "RCPT") == 0)
+ return 0;
+
+ if (!client->warned_bad_state) {
+ e_warning(client->event,
+ "Received policy query from MTA in unexpected state %s "
+ "(service can only be used for recipient restrictions)",
+ client->state);
+ }
+ client->warned_bad_state = TRUE;
+ return -1;
+}
+
+static void client_handle_request(struct quota_client *client)
+{
+ struct mail_storage_service_input input;
+ struct mail_storage_service_user *service_user;
+ struct mail_user *user;
+ struct smtp_address *rcpt;
+ const char *value = NULL, *error;
+ const char *detail ATTR_UNUSED;
+ char delim ATTR_UNUSED;
+ string_t *resp;
+ int ret;
+
+ if (client_check_mta_state(client) < 0 || client->recipient == NULL) {
+ e_debug(client->event, "Response: action=DUNNO");
+ o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n");
+ return;
+ }
+
+ if (smtp_address_parse_path(pool_datastack_create(), client->recipient,
+ SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL |
+ SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART,
+ &rcpt, &error) < 0) {
+ e_error(client->event,
+ "Client sent invalid recipient address `%s': "
+ "%s", str_sanitize(client->recipient, 256), error);
+ e_debug(client->event, "Response: action=DUNNO");
+ o_stream_nsend_str(client->conn.output, "action=DUNNO\n\n");
+ return;
+ }
+
+ i_zero(&input);
+ input.event_parent = client->event;
+ smtp_address_detail_parse_temp(quota_status_settings->recipient_delimiter,
+ rcpt, &input.username, &delim,
+ &detail);
+ ret = mail_storage_service_lookup_next(storage_service, &input,
+ &service_user, &user, &error);
+ restrict_access_allow_coredumps(TRUE);
+ if (ret == 0) {
+ e_debug(client->event, "User `%s' not found", input.username);
+ value = nouser_reply;
+ } else if (ret > 0) {
+ enum quota_alloc_result qret = quota_check(user, client->size,
+ &error);
+ if (qret == QUOTA_ALLOC_RESULT_OK) {
+ e_debug(client->event,
+ "Message is acceptable");
+ } else {
+ e_debug(client->event,
+ "Quota check failed: %s", error);
+ }
+
+ switch (qret) {
+ case QUOTA_ALLOC_RESULT_OK: /* under quota */
+ value = mail_user_plugin_getenv(user,
+ "quota_status_success");
+ if (value == NULL)
+ value = "OK";
+ break;
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ /* even over maximum quota */
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ value = mail_user_plugin_getenv(user,
+ "quota_status_toolarge");
+ /* fall through */
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ if (value == NULL)
+ value = mail_user_plugin_getenv(user,
+ "quota_status_overquota");
+ if (value == NULL)
+ value = t_strdup_printf("554 5.2.2 %s", error);
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ ret = -1;
+ break;
+ }
+ value = t_strdup(value); /* user's pool is being freed */
+ mail_user_deinit(&user);
+ mail_storage_service_user_unref(&service_user);
+ } else {
+ e_error(client->event,
+ "Failed to lookup user %s: %s", input.username, error);
+ error = "Temporary internal error";
+ }
+
+ resp = t_str_new(256);
+ if (ret < 0) {
+ /* temporary failure */
+ str_append(resp, "action=DEFER_IF_PERMIT ");
+ str_append(resp, error);
+ } else {
+ str_append(resp, "action=");
+ str_append(resp, value);
+ }
+
+ e_debug(client->event, "Response: %s", str_c(resp));
+ str_append(resp, "\n\n");
+ o_stream_nsend_str(client->conn.output, str_c(resp));
+}
+
+static int client_input_line(struct connection *conn, const char *line)
+{
+ struct quota_client *client = (struct quota_client *)conn;
+
+ e_debug(client->event, "Request: %s", str_sanitize(line, 1024));
+
+ if (*line == '\0') {
+ o_stream_cork(conn->output);
+ client_handle_request(client);
+ o_stream_uncork(conn->output);
+ client_reset(client);
+ return 1;
+ }
+ if (str_begins(line, "recipient=")) {
+ if (client->recipient == NULL)
+ client->recipient = i_strdup(line + 10);
+ } else if (str_begins(line, "size=")) {
+ if (str_to_uoff(line+5, &client->size) < 0)
+ client->size = 0;
+ } else if (str_begins(line, "protocol_state=")) {
+ if (client->state == NULL)
+ client->state = i_strdup(line + 15);
+ }
+ return 1;
+}
+
+static void client_destroy(struct connection *conn)
+{
+ struct quota_client *client = (struct quota_client *)conn;
+
+ e_debug(client->event, "Client disconnected");
+
+ connection_deinit(&client->conn);
+ client_reset(client);
+ event_unref(&client->event);
+ i_free(client);
+
+ master_service_client_connection_destroyed(master_service);
+}
+
+static struct connection_settings client_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = client_destroy,
+ .input_line = client_input_line
+};
+
+static void main_preinit(void)
+{
+ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL);
+ restrict_access_allow_coredumps(TRUE);
+}
+
+static void main_init(void)
+{
+ static const struct setting_parser_info *set_roots[] = {
+ &quota_status_setting_parser_info,
+ NULL
+ };
+ struct mail_storage_service_input input;
+ const struct setting_parser_info *user_info;
+ const struct setting_parser_context *set_parser;
+ const struct mail_user_settings *user_set;
+ const struct quota_status_settings *set;
+ const char *value, *error;
+ pool_t pool;
+
+ clients = connection_list_init(&client_set, &client_vfuncs);
+ storage_service = mail_storage_service_init(master_service, set_roots,
+ MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT |
+ MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP |
+ MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP |
+ MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR);
+
+ i_zero(&input);
+ input.service = "quota-status";
+ input.module = "mail";
+ input.username = "";
+
+ quota_status_pool = pool_alloconly_create("quota status settings", 512);
+ pool = pool_alloconly_create("service all settings", 4096);
+ if (mail_storage_service_read_settings(storage_service, &input, pool,
+ &user_info, &set_parser,
+ &error) < 0)
+ i_fatal("%s", error);
+ user_set = master_service_settings_parser_get_others(master_service,
+ set_parser)[0];
+ set = master_service_settings_get_others(master_service)[1];
+
+ quota_status_settings = settings_dup(&quota_status_setting_parser_info, set,
+ quota_status_pool);
+ value = mail_user_set_plugin_getenv(user_set, "quota_status_nouser");
+ nouser_reply = p_strdup(quota_status_pool,
+ value != NULL ? value : "REJECT Unknown user");
+ pool_unref(&pool);
+}
+
+static void main_deinit(void)
+{
+ pool_unref(&quota_status_pool);
+ connection_list_deinit(&clients);
+ mail_storage_service_deinit(&storage_service);
+}
+
+int main(int argc, char *argv[])
+{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+ int c;
+
+ protocol = QUOTA_PROTOCOL_UNKNOWN;
+ master_service = master_service_init("quota-status", service_flags,
+ &argc, &argv, "p:");
+ while ((c = master_getopt(master_service)) > 0) {
+ switch (c) {
+ case 'p':
+ if (strcmp(optarg, "postfix") == 0)
+ protocol = QUOTA_PROTOCOL_POSTFIX;
+ else
+ i_fatal("Unknown -p parameter: '%s'", optarg);
+ break;
+ default:
+ return FATAL_DEFAULT;
+ }
+ }
+ if (protocol == QUOTA_PROTOCOL_UNKNOWN)
+ i_fatal("Missing -p parameter");
+
+ master_service_init_log(master_service);
+ main_preinit();
+
+ main_init();
+ master_service_init_finish(master_service);
+ master_service_run(master_service, client_connected);
+ main_deinit();
+ master_service_deinit(&master_service);
+ return 0;
+}
diff --git a/src/plugins/quota/quota-storage.c b/src/plugins/quota/quota-storage.c
new file mode 100644
index 0000000..a1d08ee
--- /dev/null
+++ b/src/plugins/quota/quota-storage.c
@@ -0,0 +1,780 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "mail-search-build.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-private.h"
+#include "maildir-storage.h"
+#include "index-mailbox-size.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+
+#include <sys/stat.h>
+
+#define QUOTA_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_storage_module)
+#define QUOTA_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_storage_module)
+#define QUOTA_MAIL_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, quota_mail_module)
+#define QUOTA_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, quota_mailbox_list_module)
+
+struct quota_mailbox_list {
+ union mailbox_list_module_context module_ctx;
+};
+
+struct quota_mailbox {
+ union mailbox_module_context module_ctx;
+
+ struct mailbox_transaction_context *expunge_trans;
+ struct quota_transaction_context *expunge_qt;
+ ARRAY(uint32_t) expunge_uids;
+ ARRAY(uoff_t) expunge_sizes;
+ unsigned int prev_idx;
+
+ bool recalculate:1;
+ bool sync_transaction_expunge:1;
+};
+
+struct quota_user_module quota_user_module =
+ MODULE_CONTEXT_INIT(&mail_user_module_register);
+
+static MODULE_CONTEXT_DEFINE_INIT(quota_storage_module,
+ &mail_storage_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(quota_mail_module, &mail_module_register);
+static MODULE_CONTEXT_DEFINE_INIT(quota_mailbox_list_module,
+ &mailbox_list_module_register);
+
+static void quota_set_storage_error(struct quota_transaction_context *qt,
+ struct mailbox *box,
+ enum quota_alloc_result res,
+ const char *internal_err)
+{
+ const char *errstr = quota_alloc_result_errstr(res, qt);
+ struct mail_storage *storage = box->storage;
+ switch (res) {
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ mail_storage_set_error(storage, MAIL_ERROR_LIMIT, errstr);
+ break;
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ mail_storage_set_error(storage, MAIL_ERROR_NOQUOTA, errstr);
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ mailbox_set_critical(box, "quota: %s", internal_err);
+ break;
+ case QUOTA_ALLOC_RESULT_OK:
+ i_unreached();
+ }
+}
+
+static void quota_mail_expunge(struct mail *_mail)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(_mail->box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(_mail->box->storage->user);
+ union mail_module_context *qmail = QUOTA_MAIL_CONTEXT(mail);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(_mail->transaction);
+ uoff_t size;
+ int ret;
+
+ if (qt->auto_updating) {
+ qmail->super.expunge(_mail);
+ return;
+ }
+
+ /* We need to handle the situation where multiple transactions expunged
+ the mail at the same time. In here we'll just save the message's
+ physical size and do the quota freeing later when the message was
+ known to be expunged. */
+ if (quser->quota->set->vsizes)
+ ret = mail_get_virtual_size(_mail, &size);
+ else
+ ret = mail_get_physical_size(_mail, &size);
+ if (ret == 0) {
+ if (!array_is_created(&qbox->expunge_uids)) {
+ i_array_init(&qbox->expunge_uids, 64);
+ i_array_init(&qbox->expunge_sizes, 64);
+ }
+ array_push_back(&qbox->expunge_uids, &_mail->uid);
+ array_push_back(&qbox->expunge_sizes, &size);
+ if ((_mail->transaction->flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0) {
+ /* we're running dsync. if this brings the quota below
+ a negative quota warning, don't execute it, because
+ it probably was already executed by the replica. */
+ qbox->sync_transaction_expunge = TRUE;
+ } else {
+ qbox->sync_transaction_expunge = FALSE;
+ }
+ }
+
+ qmail->super.expunge(_mail);
+}
+
+static int
+quota_get_status(struct mailbox *box, enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct quota_transaction_context *qt;
+ int ret = 0;
+
+ if ((items & STATUS_CHECK_OVER_QUOTA) != 0) {
+ qt = quota_transaction_begin(box);
+ const char *error;
+ enum quota_alloc_result qret = quota_test_alloc(qt, 0, &error);
+ if (qret != QUOTA_ALLOC_RESULT_OK) {
+ quota_set_storage_error(qt, box, qret, error);
+ ret = -1;
+ }
+ quota_transaction_rollback(&qt);
+
+ if ((items & ENUM_NEGATE(STATUS_CHECK_OVER_QUOTA)) == 0) {
+ /* don't bother calling parent, it may unnecessarily
+ try to open the mailbox */
+ return ret < 0 ? -1 : 0;
+ }
+ }
+
+ if (qbox->module_ctx.super.get_status(box, items, status_r) < 0)
+ ret = -1;
+ return ret < 0 ? -1 : 0;
+}
+
+static struct mailbox_transaction_context *
+quota_mailbox_transaction_begin(struct mailbox *box,
+ enum mailbox_transaction_flags flags,
+ const char *reason)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct mailbox_transaction_context *t;
+ struct quota_transaction_context *qt;
+
+ t = qbox->module_ctx.super.transaction_begin(box, flags, reason);
+ qt = quota_transaction_begin(box);
+ qt->sync_transaction = (flags & MAILBOX_TRANSACTION_FLAG_SYNC) != 0;
+
+ MODULE_CONTEXT_SET(t, quota_storage_module, qt);
+ return t;
+}
+
+static int
+quota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx);
+
+ i_assert(qt->tmp_mail == NULL);
+
+ if (qbox->module_ctx.super.transaction_commit(ctx, changes_r) < 0) {
+ quota_transaction_rollback(&qt);
+ return -1;
+ } else {
+ (void)quota_transaction_commit(&qt);
+ return 0;
+ }
+}
+
+static void
+quota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(ctx);
+
+ i_assert(qt->tmp_mail == NULL);
+
+ qbox->module_ctx.super.transaction_rollback(ctx);
+ quota_transaction_rollback(&qt);
+}
+
+void quota_mail_allocated(struct mail *_mail)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT(_mail->box);
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct mail_vfuncs *v = mail->vlast;
+ union mail_module_context *qmail;
+
+ if (qbox == NULL)
+ return;
+
+ qmail = p_new(mail->pool, union mail_module_context, 1);
+ qmail->super = *v;
+ mail->vlast = &qmail->super;
+
+ v->expunge = quota_mail_expunge;
+ MODULE_CONTEXT_SET_SELF(mail, quota_mail_module, qmail);
+}
+
+static bool
+quota_move_requires_check(struct mailbox *dest_box, struct mailbox *src_box)
+{
+ struct mail_namespace *src_ns = src_box->list->ns;
+ struct mail_namespace *dest_ns = dest_box->list->ns;
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(src_ns->user);
+ struct quota_root *const *rootp;
+
+ array_foreach(&quser->quota->roots, rootp) {
+ bool have_src_quota, have_dest_quota;
+
+ have_src_quota = quota_root_is_namespace_visible(*rootp, src_ns);
+ have_dest_quota = quota_root_is_namespace_visible(*rootp, dest_ns);
+ if (have_src_quota == have_dest_quota) {
+ /* Both/neither have this quota */
+ } else if (have_dest_quota) {
+ /* Destination mailbox has a quota that doesn't exist
+ in source. We'll need to check if it's being
+ exceeded. */
+ return TRUE;
+ } else {
+ /* Source mailbox has a quota root that doesn't exist
+ in destination. We're not increasing the source
+ quota, so ignore it. */
+ }
+ }
+ return FALSE;
+}
+
+static int quota_check(struct mail_save_context *ctx, struct mailbox *src_box)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ enum quota_alloc_result ret;
+
+ i_assert(!ctx->moving || src_box != NULL);
+
+ if (ctx->moving &&
+ !quota_move_requires_check(ctx->transaction->box, src_box)) {
+ /* the mail is being moved. the quota won't increase (after
+ the following expunge), so allow this even if user is
+ currently over quota */
+ quota_alloc(qt, ctx->dest_mail);
+ return 0;
+ } else if (qt->failed) {
+ return 0;
+ }
+
+ const char *error;
+ ret = quota_try_alloc(qt, ctx->dest_mail, &error);
+ switch (ret) {
+ case QUOTA_ALLOC_RESULT_OK:
+ return 0;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ /* Log the error, but allow saving anyway. */
+ e_error(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ return 0;
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ /* Could not determine if there is enough space due to ongoing
+ background quota calculation, allow saving anyway. */
+ e_warning(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ return 0;
+ default:
+ quota_set_storage_error(qt, t->box, ret, error);
+ return -1;
+ }
+}
+
+static int
+quota_copy(struct mail_save_context *ctx, struct mail *mail)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box);
+
+ /* we always want to know the mail size */
+ mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL);
+
+ /* get quota before copying any mails. this avoids dovecot-vsize.lock
+ deadlocks with backends that lock mails for expunging/copying. */
+ enum quota_get_result error_res;
+ const char *error;
+ if (quota_transaction_set_limits(qt, &error_res, &error) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC) {
+ e_warning(qt->quota->event,
+ "%s - copying mail anyway", error);
+ } else {
+ e_error(qt->quota->event,
+ "%s - copying mail anyway", error);
+ }
+ }
+
+ if (qbox->module_ctx.super.copy(ctx, mail) < 0)
+ return -1;
+
+ if (ctx->copying_via_save) {
+ /* copying used saving internally, we already checked the
+ quota */
+ return 0;
+ }
+ return quota_check(ctx, mail->box);
+}
+
+static int
+quota_save_begin(struct mail_save_context *ctx, struct istream *input)
+{
+ struct mailbox_transaction_context *t = ctx->transaction;
+ struct quota_transaction_context *qt = QUOTA_CONTEXT_REQUIRE(t);
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(t->box);
+ const char *error;
+ uoff_t size;
+
+ if (!ctx->moving && i_stream_get_size(input, TRUE, &size) > 0 &&
+ !qt->failed) {
+ /* Input size is known, check for quota immediately. This
+ check isn't perfect, especially because input stream's
+ linefeeds may contain CR+LFs while physical message would
+ only contain LFs. With mbox some headers might be skipped
+ entirely.
+
+ I think these don't really matter though compared to the
+ benefit of giving "out of quota" error before sending the
+ full mail. */
+
+ enum quota_alloc_result qret = quota_test_alloc(qt, size, &error);
+ switch (qret) {
+ case QUOTA_ALLOC_RESULT_OK:
+ /* Great, there is space. */
+ break;
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ /* Log the error, but allow saving anyway. */
+ e_error(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ break;
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ /* Could not determine if there is enough space due to
+ * ongoing background quota calculation, allow saving
+ * anyway. */
+ e_warning(qt->quota->event,
+ "Failed to check if user is under quota: %s - "
+ "saving mail anyway", error);
+ break;
+ default:
+ quota_set_storage_error(qt, t->box, qret, error);
+ return -1;
+ }
+ }
+
+ /* we always want to know the mail size */
+ mail_add_temp_wanted_fields(ctx->dest_mail, MAIL_FETCH_PHYSICAL_SIZE, NULL);
+
+ /* get quota before copying any mails. this avoids dovecot-vsize.lock
+ deadlocks with backends that lock mails for expunging/copying. */
+ enum quota_get_result error_res;
+ if (quota_transaction_set_limits(qt, &error_res, &error) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ e_warning(qt->quota->event,
+ "%s - saving mail anyway", error);
+ else
+ e_error(qt->quota->event,
+ "%s - saving mail anyway", error);
+ }
+
+ return qbox->module_ctx.super.save_begin(ctx, input);
+}
+
+static int quota_save_finish(struct mail_save_context *ctx)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->transaction->box);
+ struct mailbox *src_box;
+
+ if (qbox->module_ctx.super.save_finish(ctx) < 0)
+ return -1;
+
+ src_box = ctx->copy_src_mail == NULL ? NULL : ctx->copy_src_mail->box;
+ return quota_check(ctx, src_box);
+}
+
+static void quota_mailbox_sync_cleanup(struct quota_mailbox *qbox)
+{
+ if (array_is_created(&qbox->expunge_uids)) {
+ array_clear(&qbox->expunge_uids);
+ array_clear(&qbox->expunge_sizes);
+ }
+
+ if (qbox->expunge_qt != NULL && qbox->expunge_qt->tmp_mail != NULL) {
+ mail_free(&qbox->expunge_qt->tmp_mail);
+ (void)mailbox_transaction_commit(&qbox->expunge_trans);
+ }
+ qbox->sync_transaction_expunge = FALSE;
+}
+
+static void quota_mailbox_sync_commit(struct quota_mailbox *qbox)
+{
+ quota_mailbox_sync_cleanup(qbox);
+ if (qbox->expunge_qt != NULL)
+ (void)quota_transaction_commit(&qbox->expunge_qt);
+ qbox->recalculate = FALSE;
+}
+
+static void quota_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
+ enum mailbox_sync_type sync_type)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user);
+ const uint32_t *uids;
+ const uoff_t *sizep;
+ unsigned int i, count;
+ uoff_t size;
+
+ if (qbox->module_ctx.super.sync_notify != NULL)
+ qbox->module_ctx.super.sync_notify(box, uid, sync_type);
+
+ if (sync_type != MAILBOX_SYNC_TYPE_EXPUNGE || qbox->recalculate ||
+ (box->flags & MAILBOX_FLAG_DELETE_UNSAFE) != 0) {
+ if (uid == 0) {
+ /* free the transaction before view syncing begins,
+ otherwise it'll crash. */
+ quota_mailbox_sync_cleanup(qbox);
+ }
+ return;
+ }
+
+ if (qbox->expunge_qt == NULL) {
+ qbox->expunge_qt = quota_transaction_begin(box);
+ qbox->expunge_qt->sync_transaction =
+ qbox->sync_transaction_expunge;
+ }
+ if (qbox->expunge_qt->auto_updating) {
+ /* even though backend doesn't care about size/count changes,
+ make sure count_used changes so quota_warnings are
+ executed */
+ quota_free_bytes(qbox->expunge_qt, 0);
+ return;
+ }
+
+ /* we're in the middle of syncing the mailbox, so it's a bad idea to
+ try and get the message sizes at this point. Rely on sizes that
+ we saved earlier, or recalculate the whole quota if we don't know
+ the size. */
+ if (!array_is_created(&qbox->expunge_uids) ||
+ array_is_empty(&qbox->expunge_uids)) {
+ i = count = 0;
+ } else {
+ uids = array_get(&qbox->expunge_uids, &count);
+ for (i = qbox->prev_idx; i < count; i++) {
+ if (uids[i] == uid)
+ break;
+ }
+ if (i >= count) {
+ for (i = 0; i < qbox->prev_idx; i++) {
+ if (uids[i] == uid)
+ break;
+ }
+ if (i == qbox->prev_idx)
+ i = count;
+ }
+ qbox->prev_idx = i;
+ }
+
+ if (i != count) {
+ /* we already know the size */
+ sizep = array_idx(&qbox->expunge_sizes, i);
+ quota_free_bytes(qbox->expunge_qt, *sizep);
+ /* FIXME: it's not ideal that we do the vsize update here, but
+ this is the easiest place for it for now.. maybe the mail
+ size checking code could be moved to lib-storage */
+ if (ibox->vsize_update != NULL && quser->quota->set->vsizes)
+ index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, *sizep);
+ return;
+ }
+
+ /* try to look up the size. this works only if it's cached. */
+ if (qbox->expunge_qt->tmp_mail == NULL) {
+ /* FIXME: ugly kludge to open the transaction for sync_view.
+ box->view may not have all the new messages that
+ sync_notify() notifies about, and those messages would
+ cause a quota recalculation. */
+ struct mail_index_view *box_view = box->view;
+ if (box->tmp_sync_view != NULL)
+ box->view = box->tmp_sync_view;
+ qbox->expunge_trans = mailbox_transaction_begin(box, 0, "quota");
+ box->view = box_view;
+ qbox->expunge_qt->tmp_mail =
+ mail_alloc(qbox->expunge_trans,
+ MAIL_FETCH_PHYSICAL_SIZE, NULL);
+ }
+ if (!mail_set_uid(qbox->expunge_qt->tmp_mail, uid))
+ ;
+ else if (!quser->quota->set->vsizes) {
+ if (mail_get_physical_size(qbox->expunge_qt->tmp_mail, &size) == 0) {
+ quota_free_bytes(qbox->expunge_qt, size);
+ return;
+ }
+ } else if (mail_get_virtual_size(qbox->expunge_qt->tmp_mail, &size) == 0) {
+ quota_free_bytes(qbox->expunge_qt, size);
+ if (ibox->vsize_update != NULL)
+ index_mailbox_vsize_hdr_expunge(ibox->vsize_update, uid, size);
+ } else {
+ /* there's no way to get the size. recalculate the quota. */
+ quota_recalculate(qbox->expunge_qt, QUOTA_RECALCULATE_MISSING_FREES);
+ qbox->recalculate = TRUE;
+ }
+}
+
+static int quota_mailbox_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(ctx->box);
+ int ret;
+
+ ret = qbox->module_ctx.super.sync_deinit(ctx, status_r);
+ /* update quota only after syncing is finished. the quota commit may
+ recalculate the quota and cause all mailboxes to be synced,
+ including the one we're already syncing. */
+ quota_mailbox_sync_commit(qbox);
+ return ret;
+}
+
+static void quota_roots_flush(struct quota *quota)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->backend.v.flush != NULL)
+ roots[i]->backend.v.flush(roots[i]);
+ }
+}
+
+static void quota_mailbox_close(struct mailbox *box)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(box->storage->user);
+
+ /* sync_notify() may be called outside sync_begin()..sync_deinit().
+ make sure we apply changes at close time at latest. */
+ quota_mailbox_sync_commit(qbox);
+
+ /* make sure quota backend flushes all data. this could also be done
+ somewhat later, but user.deinit() is too late, since the flushing
+ can trigger quota recalculation which isn't safe to do anymore
+ at user.deinit() when most of the loaded plugins have already been
+ deinitialized. */
+ quota_roots_flush(quser->quota);
+
+ qbox->module_ctx.super.close(box);
+}
+
+static void quota_mailbox_free(struct mailbox *box)
+{
+ struct quota_mailbox *qbox = QUOTA_CONTEXT_REQUIRE(box);
+
+ if (array_is_created(&qbox->expunge_uids)) {
+ array_free(&qbox->expunge_uids);
+ array_free(&qbox->expunge_sizes);
+ }
+ i_assert(qbox->expunge_qt == NULL ||
+ qbox->expunge_qt->tmp_mail == NULL);
+
+ qbox->module_ctx.super.free(box);
+}
+
+void quota_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct quota_mailbox *qbox;
+
+ if (QUOTA_LIST_CONTEXT(box->list) == NULL)
+ return;
+
+ if ((box->storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
+ return;
+
+ qbox = p_new(box->pool, struct quota_mailbox, 1);
+ qbox->module_ctx.super = *v;
+ box->vlast = &qbox->module_ctx.super;
+
+ v->get_status = quota_get_status;
+ v->transaction_begin = quota_mailbox_transaction_begin;
+ v->transaction_commit = quota_mailbox_transaction_commit;
+ v->transaction_rollback = quota_mailbox_transaction_rollback;
+ v->save_begin = quota_save_begin;
+ v->save_finish = quota_save_finish;
+ v->copy = quota_copy;
+ v->sync_notify = quota_mailbox_sync_notify;
+ v->sync_deinit = quota_mailbox_sync_deinit;
+ v->close = quota_mailbox_close;
+ v->free = quota_mailbox_free;
+ MODULE_CONTEXT_SET(box, quota_storage_module, qbox);
+}
+
+static void quota_mailbox_list_deinit(struct mailbox_list *list)
+{
+ struct quota_mailbox_list *qlist = QUOTA_LIST_CONTEXT(list);
+
+ i_assert(qlist != NULL);
+ quota_remove_user_namespace(list->ns);
+ qlist->module_ctx.super.deinit(list);
+}
+
+struct quota *quota_get_mail_user_quota(struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT(user);
+
+ return quser == NULL ? NULL : quser->quota;
+}
+
+static void quota_user_deinit(struct mail_user *user)
+{
+ struct quota_user *quser = QUOTA_USER_CONTEXT_REQUIRE(user);
+ struct quota_settings *quota_set = quser->quota->set;
+
+ quota_deinit(&quser->quota);
+ quser->module_ctx.super.deinit(user);
+
+ quota_settings_deinit(&quota_set);
+}
+
+void quota_mail_user_created(struct mail_user *user)
+{
+ struct mail_user_vfuncs *v = user->vlast;
+ struct quota_user *quser;
+ struct quota_settings *set;
+ struct quota *quota;
+ const char *error;
+ int ret;
+
+ if ((ret = quota_user_read_settings(user, &set, &error)) > 0) {
+ if (quota_init(set, user, &quota, &error) < 0) {
+ quota_settings_deinit(&set);
+ ret = -1;
+ }
+ }
+
+ if (ret < 0) {
+ user->error = p_strdup_printf(user->pool,
+ "Failed to initialize quota: %s", error);
+ return;
+ }
+ if (ret > 0) {
+ quser = p_new(user->pool, struct quota_user, 1);
+ quser->module_ctx.super = *v;
+ user->vlast = &quser->module_ctx.super;
+ v->deinit = quota_user_deinit;
+ quser->quota = quota;
+
+ MODULE_CONTEXT_SET(user, quota_user_module, quser);
+ } else {
+ e_debug(user->event, "quota: No quota setting - plugin disabled");
+ }
+}
+
+static struct quota_root *
+quota_find_root_for_ns(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (roots[i]->ns_prefix != NULL &&
+ strcmp(roots[i]->ns_prefix, ns->prefix) == 0)
+ return roots[i];
+ }
+ return NULL;
+}
+
+void quota_mailbox_list_created(struct mailbox_list *list)
+{
+ struct quota_mailbox_list *qlist;
+ struct quota *quota = NULL;
+ struct quota_root *root;
+ struct mail_user *quota_user;
+ bool add;
+
+ /* see if we have a quota explicitly defined for this namespace */
+ quota = quota_get_mail_user_quota(list->ns->user);
+ if (quota == NULL)
+ return;
+ root = quota_find_root_for_ns(quota, list->ns);
+ if (root != NULL) {
+ /* explicit quota root */
+ root->ns = list->ns;
+ quota_user = list->ns->user;
+ } else {
+ quota_user = list->ns->owner != NULL ?
+ list->ns->owner : list->ns->user;
+ }
+
+ if ((list->ns->flags & NAMESPACE_FLAG_NOQUOTA) != 0)
+ add = FALSE;
+ else if (list->ns->owner == NULL) {
+ /* public namespace - add quota only if namespace is
+ explicitly defined for it */
+ add = root != NULL;
+ } else {
+ /* for shared namespaces add only if the owner has quota
+ enabled */
+ add = QUOTA_USER_CONTEXT(quota_user) != NULL;
+ }
+
+ if (add) {
+ struct mailbox_list_vfuncs *v = list->vlast;
+
+ qlist = p_new(list->pool, struct quota_mailbox_list, 1);
+ qlist->module_ctx.super = *v;
+ list->vlast = &qlist->module_ctx.super;
+ v->deinit = quota_mailbox_list_deinit;
+ MODULE_CONTEXT_SET(list, quota_mailbox_list_module, qlist);
+
+ quota = quota_get_mail_user_quota(quota_user);
+ i_assert(quota != NULL);
+ quota_add_user_namespace(quota, list->ns);
+ }
+}
+
+static void quota_root_set_namespace(struct quota_root *root,
+ struct mail_namespace *namespaces)
+{
+ const struct quota_rule *rule;
+ const char *name;
+ struct mail_namespace *ns;
+ /* silence errors for autocreated (shared) users */
+ bool silent_errors = namespaces->user->autocreated;
+
+ if (root->ns_prefix != NULL && root->ns == NULL) {
+ root->ns = mail_namespace_find_prefix(namespaces,
+ root->ns_prefix);
+ if (root->ns == NULL && !silent_errors) {
+ e_error(root->quota->event,
+ "Unknown namespace: %s",
+ root->ns_prefix);
+ }
+ }
+
+ array_foreach(&root->set->rules, rule) {
+ name = rule->mailbox_mask;
+ ns = mail_namespace_find(namespaces, name);
+ if ((ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0 &&
+ !silent_errors)
+ e_error(root->quota->event,
+ "Unknown namespace: %s", name);
+ }
+}
+
+void quota_mail_namespaces_created(struct mail_namespace *namespaces)
+{
+ struct quota *quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ quota = quota_get_mail_user_quota(namespaces->user);
+ if (quota == NULL)
+ return;
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++)
+ quota_root_set_namespace(roots[i], namespaces);
+
+ quota_over_flag_check_startup(quota);
+}
diff --git a/src/plugins/quota/quota-util.c b/src/plugins/quota/quota-util.c
new file mode 100644
index 0000000..95ce369
--- /dev/null
+++ b/src/plugins/quota/quota-util.c
@@ -0,0 +1,465 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "wildcard-match.h"
+#include "quota-private.h"
+
+#include <ctype.h>
+
+#define QUOTA_DEFAULT_GRACE "10%"
+
+#define RULE_NAME_DEFAULT_FORCE "*"
+#define RULE_NAME_DEFAULT_NONFORCE "?"
+
+struct quota_rule *
+quota_root_rule_find(struct quota_root_settings *root_set, const char *name)
+{
+ struct quota_rule *rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ if (wildcard_match(name, rule->mailbox_mask))
+ return rule;
+ }
+ return NULL;
+}
+
+static struct quota_rule *
+quota_root_rule_find_exact(struct quota_root_settings *root_set,
+ const char *name)
+{
+ struct quota_rule *rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ if (strcmp(rule->mailbox_mask, name) == 0)
+ return rule;
+ }
+ return NULL;
+}
+
+static int
+quota_rule_parse_percentage(struct quota_root_settings *root_set,
+ struct quota_rule *rule,
+ int64_t *limit, const char **error_r)
+{
+ int64_t percentage = *limit;
+
+ if (percentage <= -100 || percentage >= UINT_MAX) {
+ *error_r = "Invalid percentage";
+ return -1;
+ }
+
+ if (rule == &root_set->default_rule) {
+ *error_r = "Default rule can't be a percentage";
+ return -1;
+ }
+
+ if (limit == &rule->bytes_limit)
+ rule->bytes_percent = percentage;
+ else if (limit == &rule->count_limit)
+ rule->count_percent = percentage;
+ else
+ i_unreached();
+ return 0;
+}
+
+static int quota_limit_parse(struct quota_root_settings *root_set,
+ struct quota_rule *rule, const char *unit,
+ uint64_t multiply, int64_t *limit,
+ const char **error_r)
+{
+ switch (i_toupper(*unit)) {
+ case '\0':
+ /* default */
+ break;
+ case 'B':
+ multiply = 1;
+ break;
+ case 'K':
+ multiply = 1024;
+ break;
+ case 'M':
+ multiply = 1024*1024;
+ break;
+ case 'G':
+ multiply = 1024*1024*1024;
+ break;
+ case 'T':
+ multiply = 1024ULL*1024*1024*1024;
+ break;
+ case '%':
+ multiply = 0;
+ if (quota_rule_parse_percentage(root_set, rule, limit,
+ error_r) < 0)
+ return -1;
+ break;
+ default:
+ *error_r = t_strdup_printf("Unknown unit: %s", unit);
+ return -1;
+ }
+ *limit *= multiply;
+ return 0;
+}
+
+static void
+quota_rule_recalculate_relative_rules(struct quota_rule *rule,
+ int64_t bytes_limit, int64_t count_limit)
+{
+ if (rule->bytes_percent != 0)
+ rule->bytes_limit = bytes_limit * rule->bytes_percent / 100;
+ if (rule->count_percent != 0)
+ rule->count_limit = count_limit * rule->count_percent / 100;
+}
+
+void quota_root_recalculate_relative_rules(struct quota_root_settings *root_set,
+ int64_t bytes_limit,
+ int64_t count_limit)
+{
+ struct quota_rule *rule;
+ struct quota_warning_rule *warning_rule;
+
+ array_foreach_modifiable(&root_set->rules, rule) {
+ quota_rule_recalculate_relative_rules(rule, bytes_limit,
+ count_limit);
+ }
+
+ array_foreach_modifiable(&root_set->warning_rules, warning_rule) {
+ quota_rule_recalculate_relative_rules(&warning_rule->rule,
+ bytes_limit, count_limit);
+ }
+ quota_rule_recalculate_relative_rules(&root_set->grace_rule,
+ bytes_limit, 0);
+ root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit;
+
+ if (root_set->set->initialized) {
+ e_debug(root_set->set->event,
+ "Quota root %s: Recalculated relative rules with "
+ "bytes=%lld count=%lld. Now grace=%"PRIu64, root_set->name,
+ (long long)bytes_limit, (long long)count_limit,
+ root_set->last_mail_max_extra_bytes);
+ }
+}
+
+static int
+quota_rule_parse_limits(struct quota_root_settings *root_set,
+ struct quota_rule *rule, const char *limits,
+ const char *full_rule_def,
+ bool relative_rule, const char **error_r)
+{
+ const char **args, *key, *value, *error, *p;
+ uint64_t multiply;
+ int64_t *limit;
+
+ args = t_strsplit(limits, ":");
+ for (; *args != NULL; args++) {
+ multiply = 1;
+ limit = NULL;
+
+ key = *args;
+ value = strchr(key, '=');
+ if (value == NULL)
+ value = "";
+ else
+ key = t_strdup_until(key, value++);
+
+ if (*value == '+') {
+ if (!relative_rule) {
+ *error_r = "Rule limit cannot have '+'";
+ return -1;
+ }
+ value++;
+ } else if (*value != '-' && relative_rule) {
+ e_warning(root_set->set->event, "quota root %s rule %s: "
+ "obsolete configuration for rule '%s' "
+ "should be changed to '%s=+%s'",
+ root_set->name, full_rule_def,
+ *args, key, value);
+ }
+
+ if (strcmp(key, "storage") == 0) {
+ multiply = 1024;
+ limit = &rule->bytes_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid storage limit: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "bytes") == 0) {
+ limit = &rule->bytes_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid bytes limit: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "messages") == 0) {
+ limit = &rule->count_limit;
+ if (str_parse_int64(value, limit, &p) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid bytes messages: %s", value);
+ return -1;
+ }
+ } else {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Unknown rule limit name: %s", key);
+ return -1;
+ }
+
+ if (quota_limit_parse(root_set, rule, p, multiply,
+ limit, &error) < 0) {
+ *error_r = p_strdup_printf(root_set->set->pool,
+ "Invalid rule limit value '%s': %s",
+ *args, error);
+ return -1;
+ }
+ }
+ if (!relative_rule) {
+ if (rule->bytes_limit < 0) {
+ *error_r = "Bytes limit can't be negative";
+ return -1;
+ }
+ if (rule->count_limit < 0) {
+ *error_r = "Count limit can't be negative";
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int quota_root_add_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r)
+{
+ struct quota_rule *rule;
+ const char *p, *mailbox_mask;
+ int ret = 0;
+
+ p = strchr(rule_def, ':');
+ if (p == NULL) {
+ *error_r = "Invalid rule";
+ return -1;
+ }
+
+ /* <mailbox mask>:<quota limits> */
+ mailbox_mask = t_strdup_until(rule_def, p++);
+
+ rule = quota_root_rule_find_exact(root_set, mailbox_mask);
+ if (rule == NULL) {
+ if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_NONFORCE) == 0)
+ rule = &root_set->default_rule;
+ else if (strcmp(mailbox_mask, RULE_NAME_DEFAULT_FORCE) == 0) {
+ rule = &root_set->default_rule;
+ root_set->force_default_rule = TRUE;
+ } else {
+ rule = array_append_space(&root_set->rules);
+ rule->mailbox_mask = strcasecmp(mailbox_mask, "INBOX") == 0 ? "INBOX" :
+ p_strdup(root_set->set->pool, mailbox_mask);
+ }
+ }
+
+ if (strcmp(p, "ignore") == 0) {
+ rule->ignore = TRUE;
+ e_debug(root_set->set->event,
+ "Quota rule: root=%s mailbox=%s ignored",
+ root_set->name, mailbox_mask);
+ return 0;
+ }
+
+ if (str_begins(p, "backend=")) {
+ if (root_set->backend->v.parse_rule == NULL) {
+ *error_r = "backend rule not supported";
+ ret = -1;
+ } else if (!root_set->backend->v.parse_rule(root_set, rule,
+ p + 8, error_r))
+ ret = -1;
+ } else {
+ bool relative_rule = rule != &root_set->default_rule;
+
+ if (quota_rule_parse_limits(root_set, rule, p, rule_def,
+ relative_rule, error_r) < 0)
+ ret = -1;
+ }
+
+ quota_root_recalculate_relative_rules(root_set,
+ root_set->default_rule.bytes_limit,
+ root_set->default_rule.count_limit);
+ const char *rule_plus =
+ rule == &root_set->default_rule ? "" : "+";
+
+ e_debug(root_set->set->event, "Quota rule: root=%s mailbox=%s "
+ "bytes=%s%lld%s messages=%s%lld%s",
+ root_set->name, mailbox_mask,
+ rule->bytes_limit > 0 ? rule_plus : "",
+ (long long)rule->bytes_limit,
+ rule->bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", rule->bytes_percent),
+ rule->count_limit > 0 ? rule_plus : "",
+ (long long)rule->count_limit,
+ rule->count_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", rule->count_percent));
+ return ret;
+}
+
+int quota_root_add_warning_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r)
+{
+ struct quota_warning_rule *warning;
+ struct quota_rule rule;
+ const char *p, *q;
+ int ret;
+ bool reverse = FALSE;
+
+ p = strchr(rule_def, ' ');
+ if (p == NULL || p[1] == '\0') {
+ *error_r = "No command specified";
+ return -1;
+ }
+
+ if (*rule_def == '+') {
+ /* warn when exceeding quota */
+ q = rule_def+1;
+ } else if (*rule_def == '-') {
+ /* warn when going below quota */
+ q = rule_def+1;
+ reverse = TRUE;
+ } else {
+ /* default: same as '+' */
+ q = rule_def;
+ }
+
+ i_zero(&rule);
+ ret = quota_rule_parse_limits(root_set, &rule, t_strdup_until(q, p),
+ rule_def, FALSE, error_r);
+ if (ret < 0)
+ return -1;
+
+ warning = array_append_space(&root_set->warning_rules);
+ warning->command = p_strdup(root_set->set->pool, p+1);
+ warning->rule = rule;
+ warning->reverse = reverse;
+ if (reverse)
+ root_set->have_reverse_warnings = TRUE;
+
+ quota_root_recalculate_relative_rules(root_set,
+ root_set->default_rule.bytes_limit,
+ root_set->default_rule.count_limit);
+ e_debug(root_set->set->event, "Quota warning: bytes=%"PRId64"%s "
+ "messages=%"PRId64"%s reverse=%s command=%s",
+ warning->rule.bytes_limit,
+ warning->rule.bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", warning->rule.bytes_percent),
+ warning->rule.count_limit,
+ warning->rule.count_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", warning->rule.count_percent),
+ warning->reverse ? "yes" : "no",
+ warning->command);
+ return 0;
+}
+
+int quota_root_parse_grace(struct quota_root_settings *root_set,
+ const char *value, const char **error_r)
+{
+ const char *p;
+
+ if (value == NULL) {
+ /* default */
+ value = QUOTA_DEFAULT_GRACE;
+ }
+
+ if (str_parse_int64(value, &root_set->grace_rule.bytes_limit, &p) < 0)
+ return -1;
+ if (quota_limit_parse(root_set, &root_set->grace_rule, p, 1,
+ &root_set->grace_rule.bytes_limit, error_r) < 0)
+ return -1;
+ quota_rule_recalculate_relative_rules(&root_set->grace_rule,
+ root_set->default_rule.bytes_limit, 0);
+ root_set->last_mail_max_extra_bytes = root_set->grace_rule.bytes_limit;
+ e_debug(root_set->set->event, "Quota grace: root=%s bytes=%lld%s",
+ root_set->name, (long long)root_set->grace_rule.bytes_limit,
+ root_set->grace_rule.bytes_percent == 0 ? "" :
+ t_strdup_printf(" (%u%%)", root_set->grace_rule.bytes_percent));
+ return 0;
+}
+
+bool quota_warning_match(const struct quota_warning_rule *w,
+ uint64_t bytes_before, uint64_t bytes_current,
+ uint64_t count_before, uint64_t count_current,
+ const char **reason_r)
+{
+#define QUOTA_EXCEEDED(before, current, limit) \
+ ((before) < (uint64_t)(limit) && (current) >= (uint64_t)(limit))
+ if (!w->reverse) {
+ /* over quota (default) */
+ if (QUOTA_EXCEEDED(bytes_before, bytes_current, w->rule.bytes_limit)) {
+ *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" over limit %"PRId64,
+ bytes_before, bytes_current, w->rule.bytes_limit);
+ return TRUE;
+ }
+ if (QUOTA_EXCEEDED(count_before, count_current, w->rule.count_limit)) {
+ *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" over limit %"PRId64,
+ count_before, count_current, w->rule.count_limit);
+ return TRUE;
+ }
+ } else {
+ if (QUOTA_EXCEEDED(bytes_current, bytes_before, w->rule.bytes_limit)) {
+ *reason_r = t_strdup_printf("bytes=%"PRIu64" -> %"PRIu64" below limit %"PRId64,
+ bytes_before, bytes_current, w->rule.bytes_limit);
+ return TRUE;
+ }
+ if (QUOTA_EXCEEDED(count_current, count_before, w->rule.count_limit)) {
+ *reason_r = t_strdup_printf("count=%"PRIu64" -> %"PRIu64" below limit %"PRId64,
+ count_before, count_current, w->rule.count_limit);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+bool quota_transaction_is_over(struct quota_transaction_context *ctx,
+ uoff_t size)
+{
+ if (ctx->count_used < 0) {
+ /* we've deleted some messages. we should be ok, unless we
+ were already over quota and still are after these
+ deletions. */
+ const uint64_t count_deleted = (uint64_t)-ctx->count_used;
+
+ if (ctx->count_over > 0) {
+ if (count_deleted - 1 < ctx->count_over)
+ return TRUE;
+ }
+ } else {
+ if (ctx->count_ceil < 1 ||
+ ctx->count_ceil - 1 < (uint64_t)ctx->count_used) {
+ /* count limit reached */
+ return TRUE;
+ }
+ }
+
+ if (ctx->bytes_used < 0) {
+ const uint64_t bytes_deleted = (uint64_t)-ctx->bytes_used;
+
+ /* we've deleted some messages. same logic as above. */
+ if (ctx->bytes_over > 0) {
+ if (ctx->bytes_over > bytes_deleted) {
+ /* even after deletions we're over quota */
+ return TRUE;
+ }
+ if (size > bytes_deleted - ctx->bytes_over)
+ return TRUE;
+ } else {
+ if (size > bytes_deleted &&
+ size - bytes_deleted < ctx->bytes_ceil)
+ return TRUE;
+ }
+ } else if (size == 0) {
+ /* we need to explicitly test this case, since the generic
+ check would fail if user is already over quota */
+ if (ctx->bytes_over > 0)
+ return TRUE;
+ } else {
+ if (ctx->bytes_ceil < size ||
+ ctx->bytes_ceil - size < (uint64_t)ctx->bytes_used) {
+ /* bytes limit reached */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
diff --git a/src/plugins/quota/quota.c b/src/plugins/quota/quota.c
new file mode 100644
index 0000000..3d6d8e5
--- /dev/null
+++ b/src/plugins/quota/quota.c
@@ -0,0 +1,1543 @@
+/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "ioloop.h"
+#include "net.h"
+#include "write-full.h"
+#include "eacces-error.h"
+#include "wildcard-match.h"
+#include "dict.h"
+#include "mailbox-list-private.h"
+#include "quota-private.h"
+#include "quota-fs.h"
+#include "llist.h"
+#include "program-client.h"
+#include "settings-parser.h"
+
+#include <sys/wait.h>
+
+#define DEFAULT_QUOTA_EXCEEDED_MSG \
+ "Quota exceeded (mailbox for user is full)"
+#define QUOTA_LIMIT_SET_PATH DICT_PATH_PRIVATE"quota/limit/"
+
+/* How many seconds after the userdb lookup do we still want to execute the
+ quota_over_script. This applies to quota_over_flag_lazy_check=yes and also
+ after unhibernating IMAP connections. */
+#define QUOTA_OVER_FLAG_MAX_DELAY_SECS 10
+
+struct quota_root_iter {
+ struct quota *quota;
+ struct mailbox *box;
+
+ unsigned int i;
+};
+
+unsigned int quota_module_id = 0;
+
+extern struct quota_backend quota_backend_count;
+extern struct quota_backend quota_backend_dict;
+extern struct quota_backend quota_backend_dirsize;
+extern struct quota_backend quota_backend_fs;
+extern struct quota_backend quota_backend_imapc;
+extern struct quota_backend quota_backend_maildir;
+
+static const struct quota_backend *quota_internal_backends[] = {
+#ifdef HAVE_FS_QUOTA
+ &quota_backend_fs,
+#endif
+ &quota_backend_count,
+ &quota_backend_dict,
+ &quota_backend_dirsize,
+ &quota_backend_imapc,
+ &quota_backend_maildir
+};
+
+static ARRAY(const struct quota_backend*) quota_backends;
+
+static void hidden_param_handler(struct quota_root *_root, const char *param_value);
+static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value);
+static void noenforcing_param_handler(struct quota_root *_root, const char *param_value);
+static void ns_param_handler(struct quota_root *_root, const char *param_value);
+
+struct quota_param_parser quota_param_hidden = {.param_name = "hidden", .param_handler = hidden_param_handler};
+struct quota_param_parser quota_param_ignoreunlimited = {.param_name = "ignoreunlimited", .param_handler = ignoreunlim_param_handler};
+struct quota_param_parser quota_param_noenforcing = {.param_name = "noenforcing", .param_handler = noenforcing_param_handler};
+struct quota_param_parser quota_param_ns = {.param_name = "ns=", .param_handler = ns_param_handler};
+
+static enum quota_alloc_result quota_default_test_alloc(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r);
+static void quota_over_flag_check_root(struct quota_root *root);
+
+static const struct quota_backend *quota_backend_find(const char *name)
+{
+ const struct quota_backend *const *backend;
+
+ array_foreach(&quota_backends, backend) {
+ if (strcmp((*backend)->name, name) == 0)
+ return *backend;
+ }
+
+ return NULL;
+}
+
+void quota_backend_register(const struct quota_backend *backend)
+{
+ i_assert(quota_backend_find(backend->name) == NULL);
+ array_push_back(&quota_backends, &backend);
+}
+
+void quota_backend_unregister(const struct quota_backend *backend)
+{
+ for(unsigned int i = 0; i < array_count(&quota_backends); i++) {
+ const struct quota_backend *be =
+ array_idx_elem(&quota_backends, i);
+ if (strcmp(be->name, backend->name) == 0) {
+ array_delete(&quota_backends, i, 1);
+ return;
+ }
+ }
+
+ i_unreached();
+}
+
+void quota_backends_register(void);
+void quota_backends_unregister(void);
+
+void quota_backends_register(void)
+{
+ i_array_init(&quota_backends, 8);
+ array_append(&quota_backends, quota_internal_backends,
+ N_ELEMENTS(quota_internal_backends));
+}
+
+void quota_backends_unregister(void)
+{
+ for(size_t i = 0; i < N_ELEMENTS(quota_internal_backends); i++) {
+ quota_backend_unregister(quota_internal_backends[i]);
+ }
+
+ i_assert(array_count(&quota_backends) == 0);
+ array_free(&quota_backends);
+
+}
+
+static int quota_root_add_rules(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *rule_name, *rule, *error;
+ unsigned int i;
+
+ rule_name = t_strconcat(root_name, "_rule", NULL);
+ for (i = 2;; i++) {
+ rule = mail_user_plugin_getenv(user, rule_name);
+ if (rule == NULL)
+ break;
+
+ if (quota_root_add_rule(root_set, rule, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid rule %s: %s",
+ rule, error);
+ return -1;
+ }
+ rule_name = t_strdup_printf("%s_rule%d", root_name, i);
+ }
+ return 0;
+}
+
+static int
+quota_root_add_warning_rules(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *rule_name, *rule, *error;
+ unsigned int i;
+
+ rule_name = t_strconcat(root_name, "_warning", NULL);
+ for (i = 2;; i++) {
+ rule = mail_user_plugin_getenv(user, rule_name);
+ if (rule == NULL)
+ break;
+
+ if (quota_root_add_warning_rule(root_set, rule, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid warning rule: %s",
+ rule);
+ return -1;
+ }
+ rule_name = t_strdup_printf("%s_warning%d", root_name, i);
+ }
+ return 0;
+}
+
+static int
+quota_root_parse_set(struct mail_user *user, const char *root_name,
+ struct quota_root_settings *root_set,
+ const char **error_r)
+{
+ const char *name, *value;
+
+ name = t_strconcat(root_name, "_set", NULL);
+ value = mail_user_plugin_getenv(user, name);
+ if (value == NULL)
+ return 0;
+
+ if (!str_begins(value, "dict:")) {
+ *error_r = t_strdup_printf("%s supports only dict backend", name);
+ return -1;
+ }
+ root_set->limit_set = p_strdup(root_set->set->pool, value+5);
+ return 0;
+}
+
+static int
+quota_root_settings_init(struct quota_settings *quota_set, const char *root_def,
+ struct quota_root_settings **set_r,
+ const char **error_r)
+{
+ struct quota_root_settings *root_set;
+ const struct quota_backend *backend;
+ const char *p, *args, *backend_name;
+
+ /* <backend>[:<quota root name>[:<backend args>]] */
+ p = strchr(root_def, ':');
+ if (p == NULL) {
+ backend_name = root_def;
+ args = NULL;
+ } else {
+ backend_name = t_strdup_until(root_def, p);
+ args = p + 1;
+ }
+
+ backend = quota_backend_find(backend_name);
+ if (backend == NULL) {
+ *error_r = t_strdup_printf("Unknown quota backend: %s",
+ backend_name);
+ return -1;
+ }
+
+ root_set = p_new(quota_set->pool, struct quota_root_settings, 1);
+ root_set->set = quota_set;
+ root_set->backend = backend;
+
+ if (args != NULL) {
+ /* save root's name */
+ p = strchr(args, ':');
+ if (p == NULL) {
+ root_set->name = p_strdup(quota_set->pool, args);
+ args = NULL;
+ } else {
+ root_set->name =
+ p_strdup_until(quota_set->pool, args, p);
+ args = p + 1;
+ }
+ } else {
+ root_set->name = "";
+ }
+ root_set->args = p_strdup(quota_set->pool, args);
+
+ e_debug(quota_set->event, "Quota root: name=%s backend=%s args=%s",
+ root_set->name, backend_name, args == NULL ? "" : args);
+
+ p_array_init(&root_set->rules, quota_set->pool, 4);
+ p_array_init(&root_set->warning_rules, quota_set->pool, 4);
+ array_push_back(&quota_set->root_sets, &root_set);
+ *set_r = root_set;
+ return 0;
+}
+
+static int
+quota_root_add(struct quota_settings *quota_set, struct mail_user *user,
+ const char *env, const char *root_name, const char **error_r)
+{
+ struct quota_root_settings *root_set;
+ const char *set_name, *value;
+
+ if (quota_root_settings_init(quota_set, env, &root_set, error_r) < 0)
+ return -1;
+ root_set->set_name = p_strdup(quota_set->pool, root_name);
+ if (quota_root_add_rules(user, root_name, root_set, error_r) < 0)
+ return -1;
+ if (quota_root_add_warning_rules(user, root_name, root_set, error_r) < 0)
+ return -1;
+ if (quota_root_parse_set(user, root_name, root_set, error_r) < 0)
+ return -1;
+
+ set_name = t_strconcat(root_name, "_grace", NULL);
+ value = mail_user_plugin_getenv(user, set_name);
+ if (quota_root_parse_grace(root_set, value, error_r) < 0) {
+ *error_r = t_strdup_printf("Invalid %s value '%s': %s",
+ set_name, value, *error_r);
+ return -1;
+ }
+ return 0;
+}
+
+const char *quota_alloc_result_errstr(enum quota_alloc_result res,
+ struct quota_transaction_context *qt)
+{
+ switch (res) {
+ case QUOTA_ALLOC_RESULT_OK:
+ return "OK";
+ case QUOTA_ALLOC_RESULT_BACKGROUND_CALC:
+ return "Blocked by an ongoing background quota calculation";
+ case QUOTA_ALLOC_RESULT_TEMPFAIL:
+ return "Internal quota calculation error";
+ case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
+ return "Mail size is larger than the maximum size allowed by "
+ "server configuration";
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
+ case QUOTA_ALLOC_RESULT_OVER_QUOTA:
+ return qt->quota->set->quota_exceeded_msg;
+ }
+ i_unreached();
+}
+
+int quota_user_read_settings(struct mail_user *user,
+ struct quota_settings **set_r,
+ const char **error_r)
+{
+ struct quota_settings *quota_set;
+ char root_name[5 + MAX_INT_STRLEN];
+ const char *env, *error;
+ unsigned int i;
+ pool_t pool;
+
+ pool = pool_alloconly_create("quota settings", 2048);
+ quota_set = p_new(pool, struct quota_settings, 1);
+ quota_set->pool = pool;
+ quota_set->event = event_create(user->event);
+ quota_set->test_alloc = quota_default_test_alloc;
+ quota_set->debug = user->mail_debug;
+ quota_set->quota_exceeded_msg =
+ mail_user_plugin_getenv(user, "quota_exceeded_message");
+ if (quota_set->quota_exceeded_msg == NULL)
+ quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG;
+ quota_set->vsizes = mail_user_plugin_getenv_bool(user, "quota_vsizes");
+
+ const char *max_size = mail_user_plugin_getenv(user,
+ "quota_max_mail_size");
+ if (max_size != NULL) {
+ const char *error = NULL;
+ if (settings_get_size(max_size, &quota_set->max_mail_size,
+ &error) < 0) {
+ *error_r = t_strdup_printf("quota_max_mail_size: %s",
+ error);
+ return -1;
+ }
+ }
+
+ p_array_init(&quota_set->root_sets, pool, 4);
+ if (i_strocpy(root_name, "quota", sizeof(root_name)) < 0)
+ i_unreached();
+ for (i = 2;; i++) {
+ env = mail_user_plugin_getenv(user, root_name);
+ if (env == NULL || *env == '\0')
+ break;
+
+ if (quota_root_add(quota_set, user, env, root_name,
+ &error) < 0) {
+ *error_r = t_strdup_printf("Invalid quota root %s: %s",
+ root_name, error);
+ event_unref(&quota_set->event);
+ pool_unref(&pool);
+ return -1;
+ }
+ if (i_snprintf(root_name, sizeof(root_name), "quota%d", i) < 0)
+ i_unreached();
+ }
+ if (quota_set->max_mail_size == 0 &&
+ array_count(&quota_set->root_sets) == 0) {
+ event_unref(&quota_set->event);
+ pool_unref(&pool);
+ return 0;
+ }
+
+ quota_set->initialized = TRUE;
+ *set_r = quota_set;
+ return 1;
+}
+
+void quota_settings_deinit(struct quota_settings **_quota_set)
+{
+ struct quota_settings *quota_set = *_quota_set;
+
+ *_quota_set = NULL;
+
+ event_unref(&quota_set->event);
+ pool_unref(&quota_set->pool);
+}
+
+static void quota_root_deinit(struct quota_root *root)
+{
+ pool_t pool = root->pool;
+
+ if (root->limit_set_dict != NULL)
+ dict_deinit(&root->limit_set_dict);
+ event_unref(&root->backend.event);
+ root->backend.v.deinit(root);
+ pool_unref(&pool);
+}
+
+int quota_root_default_init(struct quota_root *root, const char *args,
+ const char **error_r)
+{
+ const struct quota_param_parser default_params[] = {
+ quota_param_hidden,
+ quota_param_ignoreunlimited,
+ quota_param_noenforcing,
+ quota_param_ns,
+ {.param_name = NULL}
+ };
+ return quota_parse_parameters(root, &args, error_r, default_params, TRUE);
+}
+
+static int
+quota_root_init(struct quota_root_settings *root_set, struct quota *quota,
+ struct quota_root **root_r, const char **error_r)
+{
+ struct quota_root *root;
+
+ root = root_set->backend->v.alloc();
+ root->pool = pool_alloconly_create("quota root", 512);
+ root->set = root_set;
+ root->quota = quota;
+ root->backend = *root_set->backend;
+ root->bytes_limit = root_set->default_rule.bytes_limit;
+ root->count_limit = root_set->default_rule.count_limit;
+
+ array_create(&root->quota_module_contexts, root->pool,
+ sizeof(void *), 10);
+
+ if (root->backend.v.init != NULL) {
+ root->backend.event = event_create(quota->event);
+ event_drop_parent_log_prefixes(root->backend.event, 1);
+ event_set_forced_debug(root->backend.event, root->quota->set->debug);
+
+ if (root->backend.v.init(root, root_set->args, error_r) < 0) {
+ *error_r = t_strdup_printf("%s quota init failed: %s",
+ root->backend.name, *error_r);
+
+ event_unref(&root->backend.event);
+ return -1;
+ }
+ } else {
+ if (quota_root_default_init(root, root_set->args, error_r) < 0)
+ return -1;
+ }
+ if (root_set->default_rule.bytes_limit == 0 &&
+ root_set->default_rule.count_limit == 0 &&
+ root->disable_unlimited_tracking) {
+ quota_root_deinit(root);
+ return 0;
+ }
+ *root_r = root;
+ return 1;
+}
+
+int quota_init(struct quota_settings *quota_set, struct mail_user *user,
+ struct quota **quota_r, const char **error_r)
+{
+ struct quota *quota;
+ struct quota_root *root;
+ struct quota_root_settings *const *root_sets;
+ unsigned int i, count;
+ const char *error;
+ int ret;
+
+ quota = i_new(struct quota, 1);
+ quota->event = event_create(user->event);
+ event_set_forced_debug(quota->event, quota_set->debug);
+ event_set_append_log_prefix(quota->event, "quota: ");
+ quota->user = user;
+ quota->set = quota_set;
+ i_array_init(&quota->roots, 8);
+
+ root_sets = array_get(&quota_set->root_sets, &count);
+ i_array_init(&quota->namespaces, count);
+ for (i = 0; i < count; i++) {
+ ret = quota_root_init(root_sets[i], quota, &root, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("Quota root %s: %s",
+ root_sets[i]->name, error);
+ quota_deinit(&quota);
+ return -1;
+ }
+ if (ret > 0)
+ array_push_back(&quota->roots, &root);
+ }
+ *quota_r = quota;
+ return 0;
+}
+
+void quota_deinit(struct quota **_quota)
+{
+ struct quota *quota = *_quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++)
+ quota_root_deinit(roots[i]);
+
+ /* deinit quota roots before setting quser->quota=NULL */
+ *_quota = NULL;
+
+ array_free(&quota->roots);
+ array_free(&quota->namespaces);
+ event_unref(&quota->event);
+ i_free(quota);
+}
+
+static int quota_root_get_rule_limits(struct quota_root *root,
+ const char *mailbox_name,
+ uint64_t *bytes_limit_r,
+ uint64_t *count_limit_r,
+ bool *ignored_r,
+ const char **error_r)
+{
+ struct quota_rule *rule;
+ int64_t bytes_limit, count_limit;
+ int ret;
+
+ *ignored_r = FALSE;
+
+ if (!root->set->force_default_rule) {
+ if (root->backend.v.init_limits != NULL) {
+ const char *error;
+ if (root->backend.v.init_limits(root, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Initializing limits failed for quota backend: %s",
+ error);
+ return -1;
+ }
+ }
+ }
+
+ bytes_limit = root->bytes_limit;
+ count_limit = root->count_limit;
+
+ /* if default rule limits are 0, user has unlimited quota.
+ ignore any specific quota rules */
+ if (bytes_limit != 0 || count_limit != 0) {
+ (void)mail_namespace_find_unalias(root->quota->user->namespaces,
+ &mailbox_name);
+ rule = quota_root_rule_find(root->set, mailbox_name);
+ ret = 1;
+ } else {
+ rule = NULL;
+ ret = 0;
+ }
+
+ if (rule != NULL) {
+ if (!rule->ignore) {
+ bytes_limit += rule->bytes_limit;
+ count_limit += rule->count_limit;
+ } else {
+ bytes_limit = 0;
+ count_limit = 0;
+ *ignored_r = TRUE;
+ }
+ }
+
+ *bytes_limit_r = bytes_limit <= 0 ? 0 : bytes_limit;
+ *count_limit_r = count_limit <= 0 ? 0 : count_limit;
+ return ret;
+}
+
+static bool
+quota_is_duplicate_namespace(struct quota *quota, struct mail_namespace *ns)
+{
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+ const char *path, *path2;
+
+ if (!mailbox_list_get_root_path(ns->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path))
+ path = NULL;
+
+ namespaces = array_get(&quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ /* count namespace aliases only once. don't rely only on
+ alias_for != NULL, because the alias might have been
+ explicitly added as the wanted quota namespace. */
+ if (ns->alias_for == namespaces[i] ||
+ namespaces[i]->alias_for == ns)
+ continue;
+
+ if (path != NULL &&
+ mailbox_list_get_root_path(namespaces[i]->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path2) &&
+ strcmp(path, path2) == 0) {
+ /* duplicate path */
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
+ return TRUE;
+
+ /* this is inbox=yes namespace, but the earlier one
+ that had the same location was inbox=no. we need to
+ include the INBOX also in quota calculations, so we
+ can't just ignore this namespace. but since we've
+ already called backend's namespace_added(), we can't
+ just remove it either. so just mark the old one as
+ unwanted namespace.
+
+ an alternative would be to do a bit larger change so
+ namespaces wouldn't be added until
+ mail_namespaces_created() hook is called */
+ i_assert(quota->unwanted_ns == NULL);
+ quota->unwanted_ns = namespaces[i];
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+void quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns)
+{
+ struct quota_root *const *roots;
+ struct quota_backend **backends;
+ unsigned int i, j, count;
+
+ /* first check if there already exists a namespace with the exact same
+ path. we don't want to count them twice. */
+ if (quota_is_duplicate_namespace(quota, ns))
+ return;
+
+ array_push_back(&quota->namespaces, &ns);
+
+ roots = array_get(&quota->roots, &count);
+ /* @UNSAFE: get different backends into one array */
+ backends = t_new(struct quota_backend *, count + 1);
+ for (i = 0; i < count; i++) {
+ for (j = 0; backends[j] != NULL; j++) {
+ if (backends[j]->name == roots[i]->backend.name)
+ break;
+ }
+ if (backends[j] == NULL)
+ backends[j] = &roots[i]->backend;
+ }
+
+ for (i = 0; backends[i] != NULL; i++) {
+ if (backends[i]->v.namespace_added != NULL)
+ backends[i]->v.namespace_added(quota, ns);
+ }
+}
+
+void quota_remove_user_namespace(struct mail_namespace *ns)
+{
+ struct quota *quota;
+ struct mail_namespace *const *namespaces;
+ unsigned int i, count;
+
+ quota = ns->owner != NULL ?
+ quota_get_mail_user_quota(ns->owner) :
+ quota_get_mail_user_quota(ns->user);
+ if (quota == NULL) {
+ /* no quota for this namespace */
+ return;
+ }
+
+ namespaces = array_get(&quota->namespaces, &count);
+ for (i = 0; i < count; i++) {
+ if (namespaces[i] == ns) {
+ array_delete(&quota->namespaces, i, 1);
+ break;
+ }
+ }
+}
+
+struct quota_root_iter *
+quota_root_iter_init_user(struct mail_user *user)
+{
+ struct quota_root_iter *iter;
+
+ iter = i_new(struct quota_root_iter, 1);
+ iter->quota = quota_get_mail_user_quota(user);
+ return iter;
+}
+
+struct quota_root_iter *
+quota_root_iter_init(struct mailbox *box)
+{
+ struct quota_root_iter *iter;
+ struct mail_user *user;
+
+ user = box->list->ns->owner != NULL ?
+ box->list->ns->owner : box->list->ns->user;
+ iter = quota_root_iter_init_user(user);
+ iter->box = box;
+ return iter;
+}
+
+bool quota_root_is_namespace_visible(struct quota_root *root,
+ struct mail_namespace *ns)
+{
+ struct mailbox_list *list = ns->list;
+ struct mail_storage *storage;
+
+ /* this check works as long as there is only one storage per list */
+ if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
+ (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
+ return FALSE;
+ if (root->quota->unwanted_ns == ns)
+ return FALSE;
+
+ if (root->ns_prefix != NULL) {
+ if (root->ns != ns)
+ return FALSE;
+ } else {
+ if (ns->owner == NULL)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+quota_root_is_visible(struct quota_root *root, struct mailbox *box)
+{
+ if (!quota_root_is_namespace_visible(root, box->list->ns))
+ return FALSE;
+ if (array_count(&root->quota->roots) == 1) {
+ /* a single quota root: don't bother checking further */
+ return TRUE;
+ }
+ return root->backend.v.match_box == NULL ? TRUE :
+ root->backend.v.match_box(root, box);
+}
+
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter)
+{
+ struct quota_root *const *roots, *root = NULL;
+ unsigned int count;
+
+ if (iter->quota == NULL)
+ return NULL;
+
+ roots = array_get(&iter->quota->roots, &count);
+ if (iter->i >= count)
+ return NULL;
+
+ for (; iter->i < count; iter->i++) {
+ if (iter->box != NULL &&
+ !quota_root_is_visible(roots[iter->i], iter->box))
+ continue;
+
+ root = roots[iter->i];
+ break;
+ }
+
+ iter->i++;
+ return root;
+}
+
+void quota_root_iter_deinit(struct quota_root_iter **_iter)
+{
+ struct quota_root_iter *iter = *_iter;
+
+ *_iter = NULL;
+ i_free(iter);
+}
+
+struct quota_root *quota_root_lookup(struct mail_user *user, const char *name)
+{
+ struct quota *quota;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+
+ quota = quota_get_mail_user_quota(user);
+ if (quota == NULL)
+ return NULL;
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(roots[i]->set->name, name) == 0)
+ return roots[i];
+ }
+ return NULL;
+}
+
+const char *quota_root_get_name(struct quota_root *root)
+{
+ return root->set->name;
+}
+
+const char *const *quota_root_get_resources(struct quota_root *root)
+{
+ /* if we haven't checked the quota_over_flag yet, do it now */
+ quota_over_flag_check_root(root);
+
+ return root->backend.v.get_resources(root);
+}
+
+bool quota_root_is_hidden(struct quota_root *root)
+{
+ return root->hidden;
+}
+
+enum quota_get_result
+quota_get_resource(struct quota_root *root, const char *mailbox_name,
+ const char *name, uint64_t *value_r, uint64_t *limit_r,
+ const char **error_r)
+{
+ const char *error;
+ uint64_t bytes_limit, count_limit;
+ bool ignored, kilobytes = FALSE;
+ enum quota_get_result ret;
+
+ *value_r = *limit_r = 0;
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0) {
+ name = QUOTA_NAME_STORAGE_BYTES;
+ kilobytes = TRUE;
+ }
+
+ /* Get the value first. This call may also update quota limits if
+ they're defined externally. */
+ ret = root->backend.v.get_resource(root, name, value_r, &error);
+ if (ret == QUOTA_GET_RESULT_UNLIMITED)
+ i_panic("Quota backend %s returned QUOTA_GET_RESULT_UNLIMITED "
+ "while getting resource %s from box %s",
+ root->backend.name, name, mailbox_name);
+ else if (ret != QUOTA_GET_RESULT_LIMITED) {
+ *error_r = t_strdup_printf(
+ "quota-%s: %s", root->set->backend->name, error);
+ return ret;
+ }
+
+ if (quota_root_get_rule_limits(root, mailbox_name,
+ &bytes_limit, &count_limit,
+ &ignored, &error) < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits for mailbox %s: %s",
+ mailbox_name, error);
+ return QUOTA_GET_RESULT_INTERNAL_ERROR;
+ }
+
+ if (strcmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ *limit_r = bytes_limit;
+ else if (strcmp(name, QUOTA_NAME_MESSAGES) == 0)
+ *limit_r = count_limit;
+ else
+ *limit_r = 0;
+
+ if (kilobytes) {
+ *value_r = (*value_r + 1023) / 1024;
+ *limit_r = (*limit_r + 1023) / 1024;
+ }
+ return *limit_r == 0 ? QUOTA_GET_RESULT_UNLIMITED : QUOTA_GET_RESULT_LIMITED;
+}
+
+int quota_set_resource(struct quota_root *root, const char *name,
+ uint64_t value, const char **client_error_r)
+{
+ struct dict_transaction_context *trans;
+ const char *key, *error;
+ const struct dict_op_settings *set;
+
+ if (root->set->limit_set == NULL) {
+ *client_error_r = MAIL_ERRSTR_NO_PERMISSION;
+ return -1;
+ }
+ if (strcasecmp(name, QUOTA_NAME_STORAGE_KILOBYTES) == 0)
+ key = "storage";
+ else if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0)
+ key = "bytes";
+ else if (strcasecmp(name, QUOTA_NAME_MESSAGES) == 0)
+ key = "messages";
+ else {
+ *client_error_r = t_strdup_printf(
+ "Unsupported resource name: %s", name);
+ return -1;
+ }
+
+ if (root->limit_set_dict == NULL) {
+ struct dict_settings set;
+
+ i_zero(&set);
+ set.base_dir = root->quota->user->set->base_dir;
+ set.event_parent = root->quota->user->event;
+ if (dict_init(root->set->limit_set, &set,
+ &root->limit_set_dict, &error) < 0) {
+ e_error(root->quota->event,
+ "dict_init() failed: %s", error);
+ *client_error_r = "Internal quota limit update error";
+ return -1;
+ }
+ }
+
+ set = mail_user_get_dict_op_settings(root->ns->user);
+ trans = dict_transaction_begin(root->limit_set_dict, set);
+ key = t_strdup_printf(QUOTA_LIMIT_SET_PATH"%s", key);
+ dict_set(trans, key, dec2str(value));
+ if (dict_transaction_commit(&trans, &error) < 0) {
+ e_error(root->quota->event,
+ "dict_transaction_commit() failed: %s", error);
+ *client_error_r = "Internal quota limit update error";
+ return -1;
+ }
+ return 0;
+}
+
+struct quota_transaction_context *quota_transaction_begin(struct mailbox *box)
+{
+ struct quota_transaction_context *ctx;
+ struct quota_root *const *rootp;
+ const struct quota_rule *rule;
+ const char *mailbox_name;
+
+ ctx = i_new(struct quota_transaction_context, 1);
+ ctx->quota = box->list->ns->owner != NULL ?
+ quota_get_mail_user_quota(box->list->ns->owner) :
+ quota_get_mail_user_quota(box->list->ns->user);
+ i_assert(ctx->quota != NULL);
+
+ ctx->box = box;
+ ctx->bytes_ceil = (uint64_t)-1;
+ ctx->bytes_ceil2 = (uint64_t)-1;
+ ctx->count_ceil = (uint64_t)-1;
+
+ mailbox_name = mailbox_get_vname(box);
+ (void)mail_namespace_find_unalias(box->storage->user->namespaces,
+ &mailbox_name);
+
+ ctx->auto_updating = TRUE;
+ array_foreach(&ctx->quota->roots, rootp) {
+ if (!quota_root_is_visible(*rootp, ctx->box))
+ continue;
+
+ rule = quota_root_rule_find((*rootp)->set, mailbox_name);
+ if (rule != NULL && rule->ignore) {
+ /* This mailbox isn't included in quota. This means
+ it's also not included in quota_warnings, so make
+ sure it's fully ignored. */
+ continue;
+ }
+
+ /* If there are reverse quota_warnings, we'll need to track
+ how many bytes were expunged even with auto_updating roots.
+ (An alternative could be to get the current quota usage
+ before and after the expunges, but that's more complicated
+ and probably isn't any better.) */
+ if (!(*rootp)->auto_updating ||
+ (*rootp)->set->have_reverse_warnings)
+ ctx->auto_updating = FALSE;
+ }
+
+ if (box->storage->user->dsyncing) {
+ /* ignore quota for dsync */
+ ctx->limits_set = TRUE;
+ }
+ return ctx;
+}
+
+int quota_transaction_set_limits(struct quota_transaction_context *ctx,
+ enum quota_get_result *error_result_r,
+ const char **error_r)
+{
+ struct quota_root *const *roots;
+ const char *mailbox_name, *error;
+ unsigned int i, count;
+ uint64_t bytes_limit, count_limit, current, limit, diff;
+ bool use_grace, ignored;
+ enum quota_get_result ret;
+
+ if (ctx->limits_set)
+ return 0;
+ ctx->limits_set = TRUE;
+ mailbox_name = mailbox_get_vname(ctx->box);
+ /* use quota_grace only for LDA/LMTP */
+ use_grace = (ctx->box->flags & MAILBOX_FLAG_POST_SESSION) != 0;
+ ctx->no_quota_updates = TRUE;
+
+ /* find the lowest quota limits from all roots and use them */
+ roots = array_get(&ctx->quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ /* make sure variables get initialized */
+ bytes_limit = count_limit = 0;
+ if (!quota_root_is_visible(roots[i], ctx->box))
+ continue;
+ else if (roots[i]->no_enforcing) {
+ ignored = FALSE;
+ } else if (quota_root_get_rule_limits(roots[i], mailbox_name,
+ &bytes_limit, &count_limit,
+ &ignored, &error) < 0) {
+ ctx->failed = TRUE;
+ *error_result_r = QUOTA_GET_RESULT_INTERNAL_ERROR;
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ if (!ignored)
+ ctx->no_quota_updates = FALSE;
+
+ if (bytes_limit > 0) {
+ ret = quota_get_resource(roots[i], mailbox_name,
+ QUOTA_NAME_STORAGE_BYTES,
+ &current, &limit, &error);
+ if (ret == QUOTA_GET_RESULT_LIMITED) {
+ if (limit <= current) {
+ /* over quota */
+ ctx->bytes_ceil = 0;
+ ctx->bytes_ceil2 = 0;
+ diff = current - limit;
+ if (ctx->bytes_over < diff)
+ ctx->bytes_over = diff;
+ } else {
+ diff = limit - current;
+ if (ctx->bytes_ceil2 > diff)
+ ctx->bytes_ceil2 = diff;
+ diff += !use_grace ? 0 :
+ roots[i]->set->last_mail_max_extra_bytes;
+ if (ctx->bytes_ceil > diff)
+ ctx->bytes_ceil = diff;
+ }
+ } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ ctx->failed = TRUE;
+ *error_result_r = ret;
+ *error_r = t_strdup_printf(
+ "Failed to get quota resource "
+ QUOTA_NAME_STORAGE_BYTES" for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ }
+
+ if (count_limit > 0) {
+ ret = quota_get_resource(roots[i], mailbox_name,
+ QUOTA_NAME_MESSAGES,
+ &current, &limit, &error);
+ if (ret == QUOTA_GET_RESULT_LIMITED) {
+ if (limit <= current) {
+ /* over quota */
+ ctx->count_ceil = 0;
+ diff = current - limit;
+ if (ctx->count_over < diff)
+ ctx->count_over = diff;
+ } else {
+ diff = limit - current;
+ if (ctx->count_ceil > diff)
+ ctx->count_ceil = diff;
+ }
+ } else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ ctx->failed = TRUE;
+ *error_result_r = ret;
+ *error_r = t_strdup_printf(
+ "Failed to get quota resource "
+ QUOTA_NAME_MESSAGES" for %s: %s",
+ mailbox_name, error);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void quota_warning_execute(struct quota_root *root, const char *cmd,
+ const char *last_arg, const char *reason)
+{
+ const char *socket_path, *const *args, *error, *scheme, *ptr;
+
+ struct program_client_settings set = {
+ .client_connect_timeout_msecs = 1000,
+ .debug = root->quota->user->mail_debug,
+ };
+ struct program_client *pc;
+
+ restrict_access_init(&set.restrict_set);
+
+ e_debug(root->quota->event, "Executing warning: %s (because %s)", cmd, reason);
+
+ args = t_strsplit_spaces(cmd, " ");
+ if (last_arg != NULL) {
+ unsigned int count = str_array_length(args);
+ const char **new_args = t_new(const char *, count + 2);
+
+ memcpy(new_args, args, sizeof(const char *) * count);
+ new_args[count] = last_arg;
+ args = new_args;
+ }
+ socket_path = args[0];
+
+ if ((ptr = strchr(socket_path, ':')) != NULL) {
+ scheme = t_strcut(socket_path, ':');
+ socket_path = ptr+1;
+ } else {
+ scheme = "unix";
+ }
+
+ if (*socket_path != '/' &&
+ strcmp(scheme, "unix") == 0)
+ socket_path =
+ t_strconcat(root->quota->user->set->base_dir,
+ "/", socket_path, NULL);
+
+ socket_path = t_strdup_printf("%s:%s", scheme, socket_path);
+
+ args++;
+
+ if (program_client_create(socket_path, args, &set, TRUE,
+ &pc, &error) < 0) {
+ e_error(root->quota->event,
+ "program_client_create(%s) failed: %s", socket_path,
+ error);
+ return;
+ }
+
+ (void)program_client_run(pc);
+
+ program_client_destroy(&pc);
+}
+
+static void quota_warnings_execute(struct quota_transaction_context *ctx,
+ struct quota_root *root)
+{
+ struct quota_warning_rule *warnings;
+ unsigned int i, count;
+ uint64_t bytes_current, bytes_before, bytes_limit;
+ uint64_t count_current, count_before, count_limit;
+ const char *reason, *error;
+
+ warnings = array_get_modifiable(&root->set->warning_rules, &count);
+ if (count == 0)
+ return;
+
+ if (quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES,
+ &bytes_current, &bytes_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ e_error(root->quota->event,
+ "Failed to get quota resource "QUOTA_NAME_STORAGE_BYTES
+ ": %s", error);
+ return;
+ }
+ if (quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
+ &count_current, &count_limit, &error) == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ e_error(root->quota->event,
+ "Failed to get quota resource "QUOTA_NAME_MESSAGES
+ ": %s", error);
+ return;
+ }
+
+ if (ctx->bytes_used > 0 && bytes_current < (uint64_t)ctx->bytes_used)
+ bytes_before = 0;
+ else
+ bytes_before = (int64_t)bytes_current - ctx->bytes_used;
+
+ if (ctx->count_used > 0 && count_current < (uint64_t)ctx->count_used)
+ count_before = 0;
+ else
+ count_before = (int64_t)count_current - ctx->count_used;
+ for (i = 0; i < count; i++) {
+ if (quota_warning_match(&warnings[i],
+ bytes_before, bytes_current,
+ count_before, count_current,
+ &reason)) {
+ quota_warning_execute(root, warnings[i].command,
+ NULL, reason);
+ break;
+ }
+ }
+}
+
+int quota_transaction_commit(struct quota_transaction_context **_ctx)
+{
+ struct quota_transaction_context *ctx = *_ctx;
+ struct quota_rule *rule;
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ const char *mailbox_name;
+ int ret = 0;
+
+ *_ctx = NULL;
+
+ if (ctx->failed)
+ ret = -1;
+ else if (ctx->bytes_used != 0 || ctx->count_used != 0 ||
+ ctx->recalculate != QUOTA_RECALCULATE_DONT) T_BEGIN {
+ ARRAY(struct quota_root *) warn_roots;
+
+ mailbox_name = mailbox_get_vname(ctx->box);
+ (void)mail_namespace_find_unalias(
+ ctx->box->storage->user->namespaces, &mailbox_name);
+
+ roots = array_get(&ctx->quota->roots, &count);
+ t_array_init(&warn_roots, count);
+ for (i = 0; i < count; i++) {
+ if (!quota_root_is_visible(roots[i], ctx->box))
+ continue;
+
+ rule = quota_root_rule_find(roots[i]->set,
+ mailbox_name);
+ if (rule != NULL && rule->ignore) {
+ /* mailbox not included in quota */
+ continue;
+ }
+
+ const char *error;
+ if (roots[i]->backend.v.update(roots[i], ctx, &error) < 0) {
+ e_error(ctx->quota->event,
+ "Failed to update quota for %s: %s",
+ mailbox_name, error);
+ ret = -1;
+ }
+ else if (!ctx->sync_transaction)
+ array_push_back(&warn_roots, &roots[i]);
+ }
+ /* execute quota warnings after all updates. this makes it
+ work correctly regardless of whether backend.get_resource()
+ returns updated values before backend.update() or not.
+ warnings aren't executed when dsync bring the user over,
+ because the user probably already got the warning on the
+ other replica. */
+ array_foreach(&warn_roots, roots)
+ quota_warnings_execute(ctx, *roots);
+ } T_END;
+
+ i_free(ctx);
+ return ret;
+}
+
+static bool quota_over_flag_init_root(struct quota_root *root,
+ const char **quota_over_script_r,
+ const char **quota_over_flag_r,
+ bool *status_r)
+{
+ const char *name, *flag_mask;
+
+ *quota_over_flag_r = NULL;
+ *status_r = FALSE;
+
+ name = t_strconcat(root->set->set_name, "_over_script", NULL);
+ *quota_over_script_r = mail_user_plugin_getenv(root->quota->user, name);
+ if (*quota_over_script_r == NULL) {
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "%s unset - skipping", name);
+ return FALSE;
+ }
+
+ /* e.g.: quota_over_flag_value=TRUE or quota_over_flag_value=* */
+ name = t_strconcat(root->set->set_name, "_over_flag_value", NULL);
+ flag_mask = mail_user_plugin_getenv(root->quota->user, name);
+ if (flag_mask == NULL) {
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "%s unset - skipping", name);
+ return FALSE;
+ }
+
+ /* compare quota_over_flag's value (that comes from userdb) to
+ quota_over_flag_value and save the result. */
+ name = t_strconcat(root->set->set_name, "_over_flag", NULL);
+ *quota_over_flag_r = mail_user_plugin_getenv(root->quota->user, name);
+ *status_r = *quota_over_flag_r != NULL &&
+ wildcard_match_icase(*quota_over_flag_r, flag_mask);
+ return TRUE;
+}
+
+static void quota_over_flag_check_root(struct quota_root *root)
+{
+ const char *quota_over_script, *quota_over_flag, *error;
+ const char *const *resources;
+ unsigned int i;
+ uint64_t value, limit;
+ bool cur_overquota = FALSE;
+ bool quota_over_status;
+ enum quota_get_result ret;
+
+ if (root->quota_over_flag_checked)
+ return;
+ if (root->quota->user->session_create_time +
+ QUOTA_OVER_FLAG_MAX_DELAY_SECS < ioloop_time) {
+ /* userdb's quota_over_flag lookup is too old. */
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "Flag lookup time is too old - skipping");
+ return;
+ }
+ if (root->quota->user->session_restored) {
+ /* we don't know whether the quota_over_script was executed
+ before hibernation. just assume that it was, so we don't
+ unnecessarily call it too often. */
+ e_debug(root->quota->event, "quota_over_flag check: "
+ "Session was already hibernated - skipping");
+ return;
+ }
+ root->quota_over_flag_checked = TRUE;
+ if (!quota_over_flag_init_root(root, &quota_over_script,
+ &quota_over_flag, &quota_over_status))
+ return;
+
+ resources = quota_root_get_resources(root);
+ for (i = 0; resources[i] != NULL; i++) {
+ ret = quota_get_resource(root, "", resources[i], &value,
+ &limit, &error);
+ if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) {
+ /* can't reliably verify this */
+ e_error(root->quota->event, "Quota %s lookup failed -"
+ "can't verify quota_over_flag: %s",
+ resources[i], error);
+ return;
+ }
+ e_debug(root->quota->event, "quota_over_flag check: %s ret=%d"
+ "value=%"PRIu64" limit=%"PRIu64, resources[i], ret,
+ value, limit);
+ if (ret == QUOTA_GET_RESULT_LIMITED && value >= limit)
+ cur_overquota = TRUE;
+ }
+ e_debug(root->quota->event, "quota_over_flag=%d(%s) vs currently overquota=%d",
+ quota_over_status ? 1 : 0,
+ quota_over_flag == NULL ? "(null)" : quota_over_flag,
+ cur_overquota ? 1 : 0);
+ if (cur_overquota != quota_over_status) {
+ quota_warning_execute(root, quota_over_script, quota_over_flag,
+ "quota_over_flag mismatch");
+ }
+}
+
+void quota_over_flag_check_startup(struct quota *quota)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ const char *name;
+
+ roots = array_get(&quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ name = t_strconcat(roots[i]->set->set_name, "_over_flag_lazy_check", NULL);
+ if (!mail_user_plugin_getenv_bool(roots[i]->quota->user, name))
+ quota_over_flag_check_root(roots[i]);
+ }
+}
+
+void quota_transaction_rollback(struct quota_transaction_context **_ctx)
+{
+ struct quota_transaction_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+ i_free(ctx);
+}
+
+static int quota_get_mail_size(struct quota_transaction_context *ctx,
+ struct mail *mail, uoff_t *size_r)
+{
+ if (ctx->quota->set->vsizes)
+ return mail_get_virtual_size(mail, size_r);
+ else
+ return mail_get_physical_size(mail, size_r);
+}
+
+enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
+ struct mail *mail, const char **error_r)
+{
+ uoff_t size;
+ const char *error;
+ enum quota_get_result error_res;
+
+ if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ return QUOTA_ALLOC_RESULT_BACKGROUND_CALC;
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ if (ctx->no_quota_updates)
+ return QUOTA_ALLOC_RESULT_OK;
+
+ if (quota_get_mail_size(ctx, mail, &size) < 0) {
+ enum mail_error err;
+ error = mailbox_get_last_internal_error(mail->box, &err);
+
+ if (err == MAIL_ERROR_EXPUNGED) {
+ /* mail being copied was already expunged. it'll fail,
+ so just return success for the quota allocated. */
+ return QUOTA_ALLOC_RESULT_OK;
+ }
+ *error_r = t_strdup_printf(
+ "Failed to get mail size (box=%s, uid=%u): %s",
+ mail->box->vname, mail->uid, error);
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ enum quota_alloc_result ret = quota_test_alloc(ctx, size, error_r);
+ if (ret != QUOTA_ALLOC_RESULT_OK)
+ return ret;
+ /* with quota_try_alloc() we want to keep track of how many bytes
+ we've been adding/removing, so disable auto_updating=TRUE
+ optimization. this of course doesn't work perfectly if
+ quota_alloc() or quota_free_bytes() was already used within the same
+ transaction, but that doesn't normally happen. */
+ ctx->auto_updating = FALSE;
+ quota_alloc(ctx, mail);
+ return QUOTA_ALLOC_RESULT_OK;
+}
+
+enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
+ uoff_t size, const char **error_r)
+{
+ if (ctx->failed) {
+ *error_r = "Quota transaction has failed earlier";
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ enum quota_get_result error_res;
+ if (quota_transaction_set_limits(ctx, &error_res, error_r) < 0) {
+ if (error_res == QUOTA_GET_RESULT_BACKGROUND_CALC)
+ return QUOTA_ALLOC_RESULT_BACKGROUND_CALC;
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ uoff_t max_size = ctx->quota->set->max_mail_size;
+ if (max_size > 0 && size > max_size) {
+ *error_r = t_strdup_printf(
+ "Requested allocation size %"PRIuUOFF_T" exceeds max "
+ "mail size %"PRIuUOFF_T, size, max_size);
+ return QUOTA_ALLOC_RESULT_OVER_MAXSIZE;
+ }
+
+ if (ctx->no_quota_updates)
+ return QUOTA_ALLOC_RESULT_OK;
+ /* this is a virtual function mainly for trash plugin and similar,
+ which may automatically delete mails to stay under quota. */
+ return ctx->quota->set->test_alloc(ctx, size, error_r);
+}
+
+static enum quota_alloc_result quota_default_test_alloc(
+ struct quota_transaction_context *ctx, uoff_t size,
+ const char **error_r)
+{
+ struct quota_root *const *roots;
+ unsigned int i, count;
+ bool ignore;
+ int ret;
+
+ if (!quota_transaction_is_over(ctx, size))
+ return QUOTA_ALLOC_RESULT_OK;
+
+ /* limit reached. */
+ roots = array_get(&ctx->quota->roots, &count);
+ for (i = 0; i < count; i++) {
+ uint64_t bytes_limit, count_limit;
+
+ if (!quota_root_is_visible(roots[i], ctx->box) ||
+ roots[i]->no_enforcing)
+ continue;
+
+ const char *error;
+ ret = quota_root_get_rule_limits(roots[i],
+ mailbox_get_vname(ctx->box),
+ &bytes_limit, &count_limit,
+ &ignore, &error);
+ if (ret < 0) {
+ *error_r = t_strdup_printf(
+ "Failed to get quota root rule limits: %s",
+ error);
+ return QUOTA_ALLOC_RESULT_TEMPFAIL;
+ }
+
+ /* if size is bigger than any limit, then
+ it is bigger than the lowest limit */
+ if (bytes_limit > 0 && size > bytes_limit) {
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota limit",
+ size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
+ }
+ }
+ *error_r = t_strdup_printf(
+ "Allocating %"PRIuUOFF_T" bytes would exceed quota", size);
+ return QUOTA_ALLOC_RESULT_OVER_QUOTA;
+}
+
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
+{
+ uoff_t size;
+
+ if (!ctx->auto_updating) {
+ if (quota_get_mail_size(ctx, mail, &size) == 0)
+ ctx->bytes_used += size;
+ }
+
+ ctx->bytes_ceil = ctx->bytes_ceil2;
+ ctx->count_used++;
+}
+
+void quota_free_bytes(struct quota_transaction_context *ctx,
+ uoff_t physical_size)
+{
+ i_assert(physical_size <= INT64_MAX);
+ ctx->bytes_used -= (int64_t)physical_size;
+ ctx->count_used--;
+}
+
+void quota_recalculate(struct quota_transaction_context *ctx,
+ enum quota_recalculate recalculate)
+{
+ ctx->recalculate = recalculate;
+}
+
+static void hidden_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->hidden = TRUE;
+}
+
+static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->disable_unlimited_tracking = TRUE;
+}
+
+static void noenforcing_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
+{
+ _root->no_enforcing = TRUE;
+}
+
+static void ns_param_handler(struct quota_root *_root, const char *param_value)
+{
+ _root->ns_prefix = p_strdup(_root->pool, param_value);
+}
+
+int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r,
+ const struct quota_param_parser *valid_params, bool fail_on_unknown)
+{
+ const char *tmp_param_name, *tmp_param_val;
+ size_t tmp_param_len;
+
+ while (*args != NULL && (*args)[0] != '\0') {
+ for (; valid_params->param_name != NULL; ++valid_params) {
+ tmp_param_name = valid_params->param_name;
+ tmp_param_len = strlen(valid_params->param_name);
+ i_assert(*args != NULL);
+ if (strncmp(*args, tmp_param_name, tmp_param_len) == 0) {
+ tmp_param_val = NULL;
+ *args += tmp_param_len;
+ if (tmp_param_name[tmp_param_len - 1] == '=') {
+ const char *next_colon = strchr(*args, ':');
+ tmp_param_val = (next_colon == NULL)?
+ t_strdup(*args):
+ t_strdup_until(*args, next_colon);
+ *args = (next_colon == NULL) ? NULL : next_colon + 1;
+ }
+ else if ((*args)[0] == '\0' ||
+ (*args)[0] == ':') {
+ *args = ((*args)[0] == ':') ? *args + 1 : NULL;
+ /* in case parameter is a boolean second parameter
+ * string parameter value will be ignored by param_handler
+ * we just need some non-NULL value
+ * to indicate that argument is to be processed */
+ tmp_param_val = "";
+ }
+ if (tmp_param_val != NULL) {
+ valid_params->param_handler(root, tmp_param_val);
+ break;
+ }
+ }
+ }
+ if (valid_params->param_name == NULL) {
+ if (fail_on_unknown) {
+ *error_r = t_strdup_printf(
+ "Unknown parameter for backend %s: %s",
+ root->backend.name, *args);
+ return -1;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/plugins/quota/quota.h b/src/plugins/quota/quota.h
new file mode 100644
index 0000000..8de2d8e
--- /dev/null
+++ b/src/plugins/quota/quota.h
@@ -0,0 +1,147 @@
+#ifndef QUOTA_H
+#define QUOTA_H
+
+struct mail;
+struct mailbox;
+struct mail_user;
+
+/* Message storage size kilobytes. */
+#define QUOTA_NAME_STORAGE_KILOBYTES "STORAGE"
+/* Message storage size bytes. This is used only internally. */
+#define QUOTA_NAME_STORAGE_BYTES "STORAGE_BYTES"
+/* Number of messages. */
+#define QUOTA_NAME_MESSAGES "MESSAGE"
+
+struct quota;
+struct quota_settings;
+struct quota_root_settings;
+struct quota_root;
+struct quota_root_iter;
+struct quota_transaction_context;
+
+struct quota_param_parser {
+ char *param_name;
+ void (* param_handler)(struct quota_root *_root, const char *param_value);
+};
+
+extern struct quota_param_parser quota_param_hidden;
+extern struct quota_param_parser quota_param_ignoreunlimited;
+extern struct quota_param_parser quota_param_noenforcing;
+extern struct quota_param_parser quota_param_ns;
+
+enum quota_recalculate {
+ QUOTA_RECALCULATE_DONT = 0,
+ /* We may want to recalculate quota because we weren't able to call
+ quota_free*() correctly for all mails. Quota needs to be
+ recalculated unless the backend does the quota tracking
+ internally. */
+ QUOTA_RECALCULATE_MISSING_FREES,
+ /* doveadm quota recalc called - make sure the quota is correct */
+ QUOTA_RECALCULATE_FORCED
+};
+
+enum quota_alloc_result {
+ QUOTA_ALLOC_RESULT_OK,
+ QUOTA_ALLOC_RESULT_TEMPFAIL,
+ QUOTA_ALLOC_RESULT_OVER_MAXSIZE,
+ QUOTA_ALLOC_RESULT_OVER_QUOTA,
+ /* Mail size is larger than even the maximum allowed quota. */
+ QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT,
+ /* Blocked by ongoing background quota calculation. */
+ QUOTA_ALLOC_RESULT_BACKGROUND_CALC,
+};
+
+/* Anything <= QUOTA_GET_RESULT_INTERNAL_ERROR is an error. */
+enum quota_get_result {
+ /* Ongoing background quota calculation */
+ QUOTA_GET_RESULT_BACKGROUND_CALC,
+ /* Quota resource name doesn't exist */
+ QUOTA_GET_RESULT_UNKNOWN_RESOURCE,
+ /* Internal error */
+ QUOTA_GET_RESULT_INTERNAL_ERROR,
+
+ /* Quota limit exists and was returned successfully */
+ QUOTA_GET_RESULT_LIMITED,
+ /* Quota is unlimited, but its value was returned */
+ QUOTA_GET_RESULT_UNLIMITED,
+};
+
+const char *quota_alloc_result_errstr(enum quota_alloc_result res,
+ struct quota_transaction_context *qt);
+
+int quota_user_read_settings(struct mail_user *user,
+ struct quota_settings **set_r,
+ const char **error_r);
+void quota_settings_deinit(struct quota_settings **quota_set);
+
+/* Add a new rule too the quota root. Returns 0 if ok, -1 if rule is invalid. */
+int quota_root_add_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r);
+/* Add a new warning rule for the quota root. Returns 0 if ok, -1 if rule is
+ invalid. */
+int quota_root_add_warning_rule(struct quota_root_settings *root_set,
+ const char *rule_def, const char **error_r);
+
+/* Initialize quota for the given user. Returns 0 and quota_r on success,
+ -1 and error_r on failure. */
+int quota_init(struct quota_settings *quota_set, struct mail_user *user,
+ struct quota **quota_r, const char **error_r);
+void quota_deinit(struct quota **quota);
+
+/* List all visible quota roots. They don't need to be freed. */
+struct quota_root_iter *quota_root_iter_init_user(struct mail_user *user);
+struct quota_root_iter *quota_root_iter_init(struct mailbox *box);
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter);
+void quota_root_iter_deinit(struct quota_root_iter **iter);
+
+/* Return quota root or NULL. */
+struct quota_root *quota_root_lookup(struct mail_user *user, const char *name);
+
+/* Returns name of the quota root. */
+const char *quota_root_get_name(struct quota_root *root);
+/* Return a list of all resources set for the quota root. */
+const char *const *quota_root_get_resources(struct quota_root *root);
+/* Returns TRUE if quota root is marked as hidden (so it shouldn't be visible
+ to users via IMAP GETQUOTAROOT command). */
+bool quota_root_is_hidden(struct quota_root *root);
+
+/* Returns 1 if values were successfully returned, 0 if resource name doesn't
+ exist or isn't enabled, -1 if error. */
+enum quota_get_result
+quota_get_resource(struct quota_root *root, const char *mailbox_name,
+ const char *name, uint64_t *value_r, uint64_t *limit_r,
+ const char **error_r);
+/* Returns 0 if OK, -1 if error (eg. permission denied, invalid name). */
+int quota_set_resource(struct quota_root *root, const char *name,
+ uint64_t value, const char **client_error_r);
+
+/* Start a new quota transaction. */
+struct quota_transaction_context *quota_transaction_begin(struct mailbox *box);
+/* Commit quota transaction. Returns 0 if ok, -1 if failed. */
+int quota_transaction_commit(struct quota_transaction_context **ctx);
+/* Rollback quota transaction changes. */
+void quota_transaction_rollback(struct quota_transaction_context **ctx);
+
+/* Allocate from quota if there's space. error_r is set when result is not
+ * QUOTA_ALLOC_RESULT_OK. */
+enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
+ struct mail *mail, const char **error_r);
+/* Like quota_try_alloc(), but don't actually allocate anything. */
+enum quota_alloc_result quota_test_alloc(struct quota_transaction_context *ctx,
+ uoff_t size, const char **error_r);
+/* Update quota by allocating/freeing space used by mail. */
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail);
+void quota_free_bytes(struct quota_transaction_context *ctx,
+ uoff_t physical_size);
+/* Mark the quota to be recalculated */
+void quota_recalculate(struct quota_transaction_context *ctx,
+ enum quota_recalculate recalculate);
+
+/* Execute quota_over_scripts if needed. */
+void quota_over_flag_check_startup(struct quota *quota);
+
+/* Common quota parameters parsing loop */
+int quota_parse_parameters(struct quota_root *root, const char **args, const char **error_r,
+ const struct quota_param_parser *valid_params, bool fail_on_unknown);
+
+#endif
diff --git a/src/plugins/quota/rquota.x b/src/plugins/quota/rquota.x
new file mode 100644
index 0000000..3cd5c10
--- /dev/null
+++ b/src/plugins/quota/rquota.x
@@ -0,0 +1,139 @@
+/* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */
+/* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */
+
+/*
+ * Remote quota protocol
+ * Requires unix authentication
+ */
+
+const RQ_PATHLEN = 1024;
+
+struct sq_dqblk {
+ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
+ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
+ unsigned int rq_curblocks; /* current block count */
+ unsigned int rq_fhardlimit; /* absolute limit on allocated files */
+ unsigned int rq_fsoftlimit; /* preferred file limit */
+ unsigned int rq_curfiles; /* current # allocated files */
+ unsigned int rq_btimeleft; /* time left for excessive disk use */
+ unsigned int rq_ftimeleft; /* time left for excessive files */
+};
+
+struct getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_uid; /* Inquire about quota for uid */
+};
+
+struct setquota_args {
+ int sqa_qcmd;
+ string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int sqa_id; /* Set quota for uid */
+ sq_dqblk sqa_dqblk;
+};
+
+struct ext_getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_type; /* Type of quota info is needed about */
+ int gqa_id; /* Inquire about quota for id */
+};
+
+struct ext_setquota_args {
+ int sqa_qcmd;
+ string sqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int sqa_id; /* Set quota for id */
+ int sqa_type; /* Type of quota to set */
+ sq_dqblk sqa_dqblk;
+};
+
+/*
+ * remote quota structure
+ */
+struct rquota {
+ int rq_bsize; /* block size for block counts */
+ bool rq_active; /* indicates whether quota is active */
+ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
+ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
+ unsigned int rq_curblocks; /* current block count */
+ unsigned int rq_fhardlimit; /* absolute limit on allocated files */
+ unsigned int rq_fsoftlimit; /* preferred file limit */
+ unsigned int rq_curfiles; /* current # allocated files */
+ unsigned int rq_btimeleft; /* time left for excessive disk use */
+ unsigned int rq_ftimeleft; /* time left for excessive files */
+};
+
+enum qr_status {
+ Q_OK = 1, /* quota returned */
+ Q_NOQUOTA = 2, /* noquota for uid */
+ Q_EPERM = 3 /* no permission to access quota */
+};
+
+union getquota_rslt switch (qr_status status) {
+case Q_OK:
+ rquota gqr_rquota; /* valid if status == Q_OK */
+case Q_NOQUOTA:
+ void;
+case Q_EPERM:
+ void;
+};
+
+union setquota_rslt switch (qr_status status) {
+case Q_OK:
+ rquota sqr_rquota; /* valid if status == Q_OK */
+case Q_NOQUOTA:
+ void;
+case Q_EPERM:
+ void;
+};
+
+program RQUOTAPROG {
+ version RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2;
+
+ /*
+ * Set all quotas
+ */
+ setquota_rslt
+ RQUOTAPROC_SETQUOTA(setquota_args) = 3;
+
+ /*
+ * Get active quotas only
+ */
+ setquota_rslt
+ RQUOTAPROC_SETACTIVEQUOTA(setquota_args) = 4;
+ } = 1;
+ version EXT_RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2;
+
+ /*
+ * Set all quotas
+ */
+ setquota_rslt
+ RQUOTAPROC_SETQUOTA(ext_setquota_args) = 3;
+
+ /*
+ * Set active quotas only
+ */
+ setquota_rslt
+ RQUOTAPROC_SETACTIVEQUOTA(ext_setquota_args) = 4;
+ } = 2;
+} = 100011;
diff --git a/src/plugins/quota/test-quota-util.c b/src/plugins/quota/test-quota-util.c
new file mode 100644
index 0000000..682cffe
--- /dev/null
+++ b/src/plugins/quota/test-quota-util.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "quota-private.h"
+#include "test-common.h"
+
+struct test {
+ uint64_t limit, initial_size;
+ int64_t transaction_diff;
+ uint64_t new_size;
+ bool is_over;
+};
+
+static void test_quota_transaction_is_over(void)
+{
+#define MAXU64 (uint64_t)-1
+#define MAXS64 9223372036854775807LL
+#define MINS64 (-MAXS64 - 1LL)
+ static const struct test tests[] = {
+ /* first test only with new_size=1. these are used for both
+ count and bytes tests: */
+
+ /* limit, init, diff, new */
+ { 1, 0, 0, 1, FALSE },
+ { MAXU64, MAXU64, 0, 1, TRUE },
+ { MAXU64, MAXU64-1, 0, 1, FALSE },
+ { MAXU64, MAXU64-1, 1, 1, TRUE },
+ { MAXU64-1, MAXU64-1, 0, 1, TRUE },
+ { MAXU64-1, MAXU64-1, -1, 1, FALSE },
+ { MAXU64-2, MAXU64-1, -1, 1, TRUE },
+ { MAXU64-2, MAXU64-1, -2, 1, FALSE },
+
+ /* these are for bytes tests: */
+
+ /* limit, init, diff, new */
+ { MAXU64, MAXU64, 0, 0, FALSE },
+ { MAXU64, MAXU64-1, 1, 0, FALSE },
+ { MAXU64-1, MAXU64, 1, 0, TRUE },
+ { MAXU64-1, MAXU64, 0, 0, TRUE },
+ { MAXU64-1, MAXU64, -1, 0, FALSE },
+ { MAXU64, MAXU64, 0, 1, TRUE },
+ { MAXU64, 0, 0, MAXU64, FALSE },
+ { MAXU64, 1, 0, MAXU64, TRUE },
+ { MAXU64, 0, 1, MAXU64, TRUE },
+ { MAXU64-1, 0, 0, MAXU64, TRUE },
+ { MAXU64-1, 0, 0, MAXU64-1, FALSE },
+ { MAXU64-1, 1, 0, MAXU64-1, TRUE },
+ { MAXU64-1, 1, -1, MAXU64-1, FALSE },
+ { MAXU64, MAXU64, 0, MAXU64, TRUE },
+ };
+ struct quota_transaction_context ctx;
+ unsigned int i;
+
+ test_begin("quota transaction is over (count)");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ if (tests[i].new_size != 1)
+ continue;
+
+ i_zero(&ctx);
+ ctx.count_used = tests[i].transaction_diff;
+ if (tests[i].initial_size > tests[i].limit)
+ ctx.count_over = tests[i].initial_size - tests[i].limit;
+ else {
+ ctx.count_ceil = tests[i].limit - tests[i].initial_size;
+ i_assert(ctx.count_used < 0 ||
+ (uint64_t)ctx.count_used <= ctx.count_ceil); /* test is broken otherwise */
+ }
+ test_assert_idx(quota_transaction_is_over(&ctx, 0) == tests[i].is_over, i);
+ }
+ test_end();
+
+ test_begin("quota transaction is over (bytes)");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ i_zero(&ctx);
+ ctx.count_ceil = 1;
+ ctx.bytes_used = tests[i].transaction_diff;
+ if (tests[i].initial_size > tests[i].limit)
+ ctx.bytes_over = tests[i].initial_size - tests[i].limit;
+ else {
+ ctx.bytes_ceil = tests[i].limit - tests[i].initial_size;
+ i_assert(ctx.bytes_used < 0 ||
+ (uint64_t)ctx.bytes_used <= ctx.bytes_ceil); /* test is broken otherwise */
+ }
+ test_assert_idx(quota_transaction_is_over(&ctx, tests[i].new_size) == tests[i].is_over, i);
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_quota_transaction_is_over,
+ NULL
+ };
+ return test_run(test_functions);
+}