summaryrefslogtreecommitdiffstats
path: root/src/plugins/quota/quota-util.c
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/quota-util.c
parentInitial commit. (diff)
downloaddovecot-upstream.tar.xz
dovecot-upstream.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/quota-util.c')
-rw-r--r--src/plugins/quota/quota-util.c465
1 files changed, 465 insertions, 0 deletions
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;
+}