quota.c revision bf7dc750b95039981c0e9d728f313d50cf38a156
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2005-2017 Dovecot authors, see the included COPYING file */
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "lib.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "array.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "hash.h"
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen#include "str.h"
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen#include "ioloop.h"
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen#include "net.h"
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen#include "write-full.h"
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen#include "eacces-error.h"
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen#include "wildcard-match.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "dict.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "mailbox-list-private.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "quota-private.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "quota-fs.h"
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen#include "llist.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "program-client.h"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include "settings-parser.h"
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#include <sys/wait.h>
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#define DEFAULT_QUOTA_EXCEEDED_MSG \
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen "Quota exceeded (mailbox for user is full)"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#define QUOTA_LIMIT_SET_PATH DICT_PATH_PRIVATE"quota/limit/"
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen/* How many seconds after the userdb lookup do we still want to execute the
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen quota_over_script. This applies to quota_over_flag_lazy_check=yes and also
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen after unhibernating IMAP connections. */
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#define QUOTA_OVER_FLAG_MAX_DELAY_SECS 10
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstruct quota_root_iter {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct quota *quota;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct mailbox *box;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen unsigned int i;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen};
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainenunsigned int quota_module_id = 0;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainenextern struct quota_backend quota_backend_count;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenextern struct quota_backend quota_backend_dict;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenextern struct quota_backend quota_backend_dirsize;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainenextern struct quota_backend quota_backend_fs;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenextern struct quota_backend quota_backend_maildir;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainenstatic const struct quota_backend *quota_backends[] = {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen#ifdef HAVE_FS_QUOTA
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &quota_backend_fs,
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen#endif
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &quota_backend_count,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &quota_backend_dict,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &quota_backend_dirsize,
f323e3f0de9841f399aba5919e3f25652a88fa65Timo Sirainen &quota_backend_maildir
f323e3f0de9841f399aba5919e3f25652a88fa65Timo Sirainen};
f323e3f0de9841f399aba5919e3f25652a88fa65Timo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstatic enum quota_alloc_result quota_default_test_alloc(
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen struct quota_transaction_context *ctx, uoff_t size);
10399559650f552a23949772be79eb6a80198c5aTimo Sirainenstatic void quota_over_flag_check_root(struct quota_root *root);
10399559650f552a23949772be79eb6a80198c5aTimo Sirainen
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainenstatic const struct quota_backend *quota_backend_find(const char *name)
39ea5717264668e2c7f9f7986eb821d21785f47fTimo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen unsigned int i;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainen for (i = 0; i < N_ELEMENTS(quota_backends); i++) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (strcmp(quota_backends[i]->name, name) == 0)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return quota_backends[i];
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen }
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen return NULL;
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen}
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainenstatic int quota_root_add_rules(struct mail_user *user, const char *root_name,
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct quota_root_settings *root_set,
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainen const char **error_r)
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen{
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen const char *rule_name, *rule, *error;
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen unsigned int i;
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen rule_name = t_strconcat(root_name, "_rule", NULL);
eca38954bcf972618f6b85932a3690acbd2b673aTimo Sirainen for (i = 2;; i++) {
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen rule = mail_user_plugin_getenv(user, rule_name);
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen if (rule == NULL)
8f2eb1ee9ec07661bd50275da99b5f351972a49aTimo Sirainen break;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainen if (quota_root_add_rule(root_set, rule, &error) < 0) {
a6a6ad107e509cf8952a28f740eb2023284497b9Timo Sirainen *error_r = t_strdup_printf("Invalid rule %s: %s",
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen rule, error);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen rule_name = t_strdup_printf("%s_rule%d", root_name, i);
10399559650f552a23949772be79eb6a80198c5aTimo Sirainen }
dc599de6096c51e6c922e069bfbbcb7d68c50ffaStephan Bosch return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstatic int
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenquota_root_add_warning_rules(struct mail_user *user, const char *root_name,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen struct quota_root_settings *root_set,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const char **error_r)
a943ed0f901e312445fd393249b91932797bba79Josef 'Jeff' Sipek{
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen const char *rule_name, *rule, *error;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen unsigned int i;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen rule_name = t_strconcat(root_name, "_warning", NULL);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen for (i = 2;; i++) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen rule = mail_user_plugin_getenv(user, rule_name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (rule == NULL)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen break;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (quota_root_add_warning_rule(root_set, rule, &error) < 0) {
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen *error_r = t_strdup_printf("Invalid warning rule: %s",
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen rule);
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen return -1;
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen }
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen rule_name = t_strdup_printf("%s_warning%d", root_name, i);
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
62ff6002b1e37a42303c2c0107f324860232e204Timo Sirainenstatic int
62ff6002b1e37a42303c2c0107f324860232e204Timo Sirainenquota_root_parse_set(struct mail_user *user, const char *root_name,
62ff6002b1e37a42303c2c0107f324860232e204Timo Sirainen struct quota_root_settings *root_set,
62ff6002b1e37a42303c2c0107f324860232e204Timo Sirainen const char **error_r)
62ff6002b1e37a42303c2c0107f324860232e204Timo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const char *name, *value;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen name = t_strconcat(root_name, "_set", NULL);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen value = mail_user_plugin_getenv(user, name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (value == NULL)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (strncmp(value, "dict:", 5) != 0) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen *error_r = t_strdup_printf("%s supports only dict backend", name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root_set->limit_set = p_strdup(root_set->set->pool, value+5);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstatic int
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenquota_root_settings_init(struct quota_settings *quota_set, const char *root_def,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen struct quota_root_settings **set_r,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char **error_r)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen{
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct quota_root_settings *root_set;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const struct quota_backend *backend;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const char *p, *args, *backend_name;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen /* <backend>[:<quota root name>[:<backend args>]] */
a943ed0f901e312445fd393249b91932797bba79Josef 'Jeff' Sipek p = strchr(root_def, ':');
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (p == NULL) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen backend_name = root_def;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen args = NULL;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen } else {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen backend_name = t_strdup_until(root_def, p);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen args = p + 1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen backend = quota_backend_find(backend_name);
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen if (backend == NULL) {
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen *error_r = t_strdup_printf("Unknown quota backend: %s",
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen backend_name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen }
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen root_set = p_new(quota_set->pool, struct quota_root_settings, 1);
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen root_set->set = quota_set;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen root_set->backend = backend;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (args != NULL) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen /* save root's name */
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen p = strchr(args, ':');
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if (p == NULL) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen root_set->name = p_strdup(quota_set->pool, args);
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen args = NULL;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen } else {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root_set->name =
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen p_strdup_until(quota_set->pool, args, p);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen args = p + 1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen } else {
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi root_set->name = "";
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen }
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen root_set->args = p_strdup(quota_set->pool, args);
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen if (quota_set->debug) {
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen i_debug("Quota root: name=%s backend=%s args=%s",
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen root_set->name, backend_name, args == NULL ? "" : args);
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen }
3350b29fce44f8bc8fa015dad57024a8de301d38Timo Sirainen
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen p_array_init(&root_set->rules, quota_set->pool, 4);
c224fff79d18480a65e9b4504b891b8ea176f5b1Timo Sirainen p_array_init(&root_set->warning_rules, quota_set->pool, 4);
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen array_append(&quota_set->root_sets, &root_set, 1);
bdb0f594a5673a0c5a16b92dc49eb2a8a66bdaceTimo Sirainen *set_r = root_set;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstatic int
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenquota_root_add(struct quota_settings *quota_set, struct mail_user *user,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen const char *env, const char *root_name, const char **error_r)
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen struct quota_root_settings *root_set;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const char *set_name, *value;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if (quota_root_settings_init(quota_set, env, &root_set, error_r) < 0)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root_set->set_name = p_strdup(quota_set->pool, root_name);
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen if (quota_root_add_rules(user, root_name, root_set, error_r) < 0)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (quota_root_add_warning_rules(user, root_name, root_set, error_r) < 0)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (quota_root_parse_set(user, root_name, root_set, error_r) < 0)
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen set_name = t_strconcat(root_name, "_grace", NULL);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen value = mail_user_plugin_getenv(user, set_name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (quota_root_parse_grace(root_set, value, error_r) < 0) {
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen *error_r = t_strdup_printf("Invalid %s value '%s': %s",
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen set_name, value, *error_r);
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen return 0;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen}
8ad53e0bb29f61350f608fc519210f2442c20775Timo Sirainen
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainenconst char *quota_alloc_result_errstr(enum quota_alloc_result res,
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen struct quota_transaction_context *qt)
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen{
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen switch (res) {
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen case QUOTA_ALLOC_RESULT_OK:
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen return "OK";
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen case QUOTA_ALLOC_RESULT_TEMPFAIL:
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen return "Internal quota calculation error";
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen case QUOTA_ALLOC_RESULT_OVER_MAXSIZE:
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return "Mail size is larger than the maximum size allowed by "
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen "server configuration";
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen case QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT:
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen case QUOTA_ALLOC_RESULT_OVER_QUOTA:
37fed1bc1545f7eb1755b61d6a5ac4d083a693b3Timo Sirainen return qt->quota->set->quota_exceeded_msg;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen i_unreached();
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainenint quota_user_read_settings(struct mail_user *user,
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen struct quota_settings **set_r,
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen const char **error_r)
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen{
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen struct quota_settings *quota_set;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen char root_name[5 + MAX_INT_STRLEN + 1];
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen const char *env, *error;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen unsigned int i;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen pool_t pool;
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen pool = pool_alloconly_create("quota settings", 2048);
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen quota_set = p_new(pool, struct quota_settings, 1);
8d25b6ad05b99e75613cb045a121efd51e6afbb6Timo Sirainen quota_set->pool = pool;
8d25b6ad05b99e75613cb045a121efd51e6afbb6Timo Sirainen quota_set->test_alloc = quota_default_test_alloc;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen quota_set->debug = user->mail_debug;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen quota_set->quota_exceeded_msg =
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen mail_user_plugin_getenv(user, "quota_exceeded_message");
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen if (quota_set->quota_exceeded_msg == NULL)
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen quota_set->quota_exceeded_msg = DEFAULT_QUOTA_EXCEEDED_MSG;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen quota_set->vsizes = mail_user_plugin_getenv_bool(user, "quota_vsizes");
c73415e93ecf1c699ef054d2b179b10976fa23f3Timo Sirainen
c73415e93ecf1c699ef054d2b179b10976fa23f3Timo Sirainen const char *max_size = mail_user_plugin_getenv(user,
c73415e93ecf1c699ef054d2b179b10976fa23f3Timo Sirainen "quota_max_mail_size");
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen if (max_size != NULL) {
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen const char *error = NULL;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (settings_get_size(max_size, &quota_set->max_mail_size,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &error) < 0) {
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen *error_r = t_strdup_printf("quota_max_mail_size: %s",
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen error);
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen return -1;
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen }
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen }
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen p_array_init(&quota_set->root_sets, pool, 4);
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen if (i_strocpy(root_name, "quota", sizeof(root_name)) < 0)
76b4207273534f71365bc7f900c23a5160692802Timo Sirainen i_unreached();
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen for (i = 2;; i++) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen env = mail_user_plugin_getenv(user, root_name);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (env == NULL || *env == '\0')
8d25b6ad05b99e75613cb045a121efd51e6afbb6Timo Sirainen break;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
8d25b6ad05b99e75613cb045a121efd51e6afbb6Timo Sirainen if (quota_root_add(quota_set, user, env, root_name,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen &error) < 0) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen *error_r = t_strdup_printf("Invalid quota root %s: %s",
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen root_name, error);
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen pool_unref(&pool);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (i_snprintf(root_name, sizeof(root_name), "quota%d", i) < 0)
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen i_unreached();
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
055389c58fa3915e12fb4e72ec86782ce77c5c72Timo Sirainen if (quota_set->max_mail_size == 0 &&
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen array_count(&quota_set->root_sets) == 0) {
9c7f6dbf65ca01026e5f9c8c8b67c7e629c0b5e7Timo Sirainen pool_unref(&pool);
8d25b6ad05b99e75613cb045a121efd51e6afbb6Timo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen quota_set->initialized = TRUE;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen *set_r = quota_set;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 1;
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenvoid quota_settings_deinit(struct quota_settings **_quota_set)
dba8af1faaf9fd3957254bb6f2234b285f77096fTimo Sirainen{
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen struct quota_settings *quota_set = *_quota_set;
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen *_quota_set = NULL;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen pool_unref(&quota_set->pool);
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen}
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenstatic void quota_root_deinit(struct quota_root *root)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen pool_t pool = root->pool;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen if (root->limit_set_dict != NULL)
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen dict_deinit(&root->limit_set_dict);
2ac5f36aa7c2e7a07ba8815d43a6d7483f62e74cTimo Sirainen root->backend.v.deinit(root);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen pool_unref(&pool);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen
a75d470c9223a75801418fcdda258885c36317e0Timo Sirainenint quota_root_default_init(struct quota_root *root, const char *args,
a75d470c9223a75801418fcdda258885c36317e0Timo Sirainen const char **error_r)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen const char *const *tmp;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (args == NULL)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen tmp = t_strsplit_spaces(args, " ");
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen for (; *tmp != NULL; tmp++) {
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen if (strcmp(*tmp, "noenforcing") == 0)
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen root->no_enforcing = TRUE;
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen else if (strcmp(*tmp, "hidden") == 0)
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen root->hidden = TRUE;
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen else if (strcmp(*tmp, "ignoreunlimited") == 0)
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen root->disable_unlimited_tracking = TRUE;
e48f289d2e5b2546a2c5dcc90f7ab624cc58cca2Stephan Bosch else
e48f289d2e5b2546a2c5dcc90f7ab624cc58cca2Stephan Bosch break;
e48f289d2e5b2546a2c5dcc90f7ab624cc58cca2Stephan Bosch }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (*tmp != NULL) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen *error_r = t_strdup_printf(
3c63c219ae7854b4f1d44a671a65572aa242cbcfTimo Sirainen "Unknown parameter for backend %s: %s",
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root->backend.name, *tmp);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return 0;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainenstatic int
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenquota_root_init(struct quota_root_settings *root_set, struct quota *quota,
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen struct quota_root **root_r, const char **error_r)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen{
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen struct quota_root *root;
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root = root_set->backend->v.alloc();
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen root->resource_ret = -1;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root->pool = pool_alloconly_create("quota root", 512);
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen root->set = root_set;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root->quota = quota;
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen root->backend = *root_set->backend;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root->bytes_limit = root_set->default_rule.bytes_limit;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen root->count_limit = root_set->default_rule.count_limit;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen array_create(&root->quota_module_contexts, root->pool,
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen sizeof(void *), 10);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen if (root->backend.v.init != NULL) {
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen if (root->backend.v.init(root, root_set->args, error_r) < 0) {
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen *error_r = t_strdup_printf("%s quota init failed: %s",
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen root->backend.name, *error_r);
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen return -1;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen }
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen } else {
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen if (quota_root_default_init(root, root_set->args, error_r) < 0)
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen return -1;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen }
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen if (root_set->default_rule.bytes_limit == 0 &&
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen root_set->default_rule.count_limit == 0 &&
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen root->disable_unlimited_tracking) {
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen quota_root_deinit(root);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen return 0;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen }
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen *root_r = root;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen return 1;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen}
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainenint quota_init(struct quota_settings *quota_set, struct mail_user *user,
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen struct quota **quota_r, const char **error_r)
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen{
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen struct quota *quota;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen struct quota_root *root;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen struct quota_root_settings *const *root_sets;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen unsigned int i, count;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen const char *error;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen int ret;
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen quota = i_new(struct quota, 1);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen quota->user = user;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen quota->set = quota_set;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen i_array_init(&quota->roots, 8);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen root_sets = array_get(&quota_set->root_sets, &count);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen i_array_init(&quota->namespaces, count);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen for (i = 0; i < count; i++) {
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen ret = quota_root_init(root_sets[i], quota, &root, &error);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen if (ret < 0) {
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen *error_r = t_strdup_printf("Quota root %s: %s",
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen root_sets[i]->name, error);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen quota_deinit(&quota);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen return -1;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen }
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen if (ret > 0)
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen array_append(&quota->roots, &root, 1);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen }
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen *quota_r = quota;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen return 0;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen}
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainenvoid quota_deinit(struct quota **_quota)
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen{
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen struct quota *quota = *_quota;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen struct quota_root *const *roots;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen unsigned int i, count;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen roots = array_get(&quota->roots, &count);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen for (i = 0; i < count; i++)
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen quota_root_deinit(roots[i]);
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen /* deinit quota roots before setting quser->quota=NULL */
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen *_quota = NULL;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen array_free(&quota->roots);
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen array_free(&quota->namespaces);
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen i_free(quota);
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen}
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainenstatic int quota_root_get_rule_limits(struct quota_root *root,
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen const char *mailbox_name,
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen uint64_t *bytes_limit_r,
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen uint64_t *count_limit_r,
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen bool *ignored_r)
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen{
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen struct quota_rule *rule;
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen int64_t bytes_limit, count_limit;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen bool enabled;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen *ignored_r = FALSE;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen if (!root->set->force_default_rule) {
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen if (root->backend.v.init_limits != NULL) {
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen if (root->backend.v.init_limits(root) < 0)
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen return -1;
c85f661daf164687fc5af22d74902f94c26597d0Timo Sirainen }
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen }
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen bytes_limit = root->bytes_limit;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen count_limit = root->count_limit;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen /* if default rule limits are 0, user has unlimited quota.
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen ignore any specific quota rules */
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen enabled = bytes_limit != 0 || count_limit != 0;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen (void)mail_namespace_find_unalias(root->quota->user->namespaces,
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen &mailbox_name);
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen rule = enabled ? quota_root_rule_find(root->set, mailbox_name) : NULL;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (rule != NULL) {
c719f74d3fd41d9b9fea0edaea1e00ab90da72dcTimo Sirainen if (!rule->ignore) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen bytes_limit += rule->bytes_limit;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen count_limit += rule->count_limit;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen } else {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen bytes_limit = 0;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen count_limit = 0;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen *ignored_r = TRUE;
c719f74d3fd41d9b9fea0edaea1e00ab90da72dcTimo Sirainen }
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen }
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen *bytes_limit_r = bytes_limit <= 0 ? 0 : bytes_limit;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen *count_limit_r = count_limit <= 0 ? 0 : count_limit;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen return enabled ? 1 : 0;
c719f74d3fd41d9b9fea0edaea1e00ab90da72dcTimo Sirainen}
3bc62efe513ebc7450cffe9a4e8f0b07424bf190Timo Sirainen
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenstatic bool
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainenquota_is_duplicate_namespace(struct quota *quota, struct mail_namespace *ns)
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen{
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen struct mail_namespace *const *namespaces;
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen unsigned int i, count;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen const char *path, *path2;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
c719f74d3fd41d9b9fea0edaea1e00ab90da72dcTimo Sirainen if (!mailbox_list_get_root_path(ns->list,
c719f74d3fd41d9b9fea0edaea1e00ab90da72dcTimo Sirainen MAILBOX_LIST_PATH_TYPE_MAILBOX, &path))
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return TRUE;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen namespaces = array_get(&quota->namespaces, &count);
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen for (i = 0; i < count; i++) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (mailbox_list_get_root_path(namespaces[i]->list,
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen MAILBOX_LIST_PATH_TYPE_MAILBOX, &path2) &&
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen strcmp(path, path2) == 0) {
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen /* duplicate path */
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) == 0)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return TRUE;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen /* this is inbox=yes namespace, but the earlier one
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen that had the same location was inbox=no. we need to
b5052fbfdbc2678cc8f12899afe55c998f43b740Timo Sirainen include the INBOX also in quota calculations, so we
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen can't just ignore this namespace. but since we've
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen already called backend's namespace_added(), we can't
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen just remove it either. so just mark the old one as
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen unwanted namespace.
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen an alternative would be to do a bit larger change so
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen namespaces wouldn't be added until
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen mail_namespaces_created() hook is called */
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen i_assert(quota->unwanted_ns == NULL);
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen quota->unwanted_ns = namespaces[i];
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return FALSE;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return FALSE;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen}
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainenvoid quota_add_user_namespace(struct quota *quota, struct mail_namespace *ns)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen{
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen struct quota_root *const *roots;
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen struct quota_backend **backends;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen unsigned int i, j, count;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen /* first check if there already exists a namespace with the exact same
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen path. we don't want to count them twice. */
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (quota_is_duplicate_namespace(quota, ns))
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen array_append(&quota->namespaces, &ns, 1);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen roots = array_get(&quota->roots, &count);
262eeae35ee285981b37294628ec8d054daa1b23Timo Sirainen /* @UNSAFE: get different backends into one array */
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen backends = t_new(struct quota_backend *, count + 1);
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen for (i = 0; i < count; i++) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen for (j = 0; backends[j] != NULL; j++) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (backends[j]->name == roots[i]->backend.name)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen break;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen }
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen if (backends[j] == NULL)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen backends[j] = &roots[i]->backend;
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen }
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen for (i = 0; backends[i] != NULL; i++) {
97943a36e08923d625898f5ca8ffd38325a3986dTimo Sirainen if (backends[i]->v.namespace_added != NULL)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen backends[i]->v.namespace_added(quota, ns);
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen }
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen}
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainenvoid quota_remove_user_namespace(struct mail_namespace *ns)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen{
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct quota *quota;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen struct mail_namespace *const *namespaces;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen unsigned int i, count;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen quota = ns->owner != NULL ?
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen quota_get_mail_user_quota(ns->owner) :
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen quota_get_mail_user_quota(ns->user);
b66d803de86bfb411165b3465b0d9ef64ecfe2a1Timo Sirainen if (quota == NULL) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen /* no quota for this namespace */
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen return;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen }
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen namespaces = array_get(&quota->namespaces, &count);
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen for (i = 0; i < count; i++) {
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen if (namespaces[i] == ns) {
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen array_delete(&quota->namespaces, i, 1);
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen break;
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen }
e5ff2112aea089f3de2badf9b1635677791d1384Timo Sirainen }
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen}
b141c303bf09b6ae43e1eb4aac1e1a6b796b9d35Timo Sirainen
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainenstruct quota_root_iter *
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenquota_root_iter_init(struct mailbox *box)
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi{
313c43343f711f04a8d4d1f8dd21aaab5c16b2d2Timo Sirainen struct quota_root_iter *iter;
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen iter = i_new(struct quota_root_iter, 1);
a75d470c9223a75801418fcdda258885c36317e0Timo Sirainen iter->quota = box->list->ns->owner != NULL ?
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen quota_get_mail_user_quota(box->list->ns->owner) :
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen quota_get_mail_user_quota(box->list->ns->user);
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen iter->box = box;
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen return iter;
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen}
1f366614aaafcc9496ff85b25988f19c3254ab7cTimo Sirainen
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainenbool quota_root_is_namespace_visible(struct quota_root *root,
3ccab0bac68040f179a7de45c516cec258e28fdbTimo Sirainen struct mail_namespace *ns)
efd9d73a7fba2830431aa1186fd65372f6631399Timo Sirainen{
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen struct mailbox_list *list = ns->list;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen struct mail_storage *storage;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
efd9d73a7fba2830431aa1186fd65372f6631399Timo Sirainen /* this check works as long as there is only one storage per list */
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen if (mailbox_list_get_storage(&list, "", &storage) == 0 &&
efd9d73a7fba2830431aa1186fd65372f6631399Timo Sirainen (storage->class_flags & MAIL_STORAGE_CLASS_FLAG_NOQUOTA) != 0)
efd9d73a7fba2830431aa1186fd65372f6631399Timo Sirainen return FALSE;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (root->quota->unwanted_ns == ns)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return FALSE;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (root->ns_prefix != NULL) {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (root->ns != ns)
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen return FALSE;
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen } else {
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (ns->owner == NULL)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return FALSE;
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen }
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen return TRUE;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen}
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainenstatic bool
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainenquota_root_is_visible(struct quota_root *root, struct mailbox *box,
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen bool enforce)
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen{
f739c92a9237db03327dc82e3792e39c160a1e4dTimo Sirainen if (root->no_enforcing && enforce) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen /* we don't want to include this root in quota enforcing */
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return FALSE;
7b032348d7bbb93ff96188289d3dfc1899b9abb3Josef 'Jeff' Sipek }
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (!quota_root_is_namespace_visible(root, box->list->ns))
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return FALSE;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (array_count(&root->quota->roots) == 1) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen /* a single quota root: don't bother checking further */
c2a66e7950cb4d3fc4d68e4480ea8f39bdd7c871Timo Sirainen return TRUE;
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen }
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen return root->backend.v.match_box == NULL ? TRUE :
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen root->backend.v.match_box(root, box);
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen}
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainenstruct quota_root *quota_root_iter_next(struct quota_root_iter *iter)
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen{
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen struct quota_root *const *roots, *root = NULL;
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen unsigned int count;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen uint64_t value, limit;
efe78d3ba24fc866af1c79b9223dc0809ba26cadStephan Bosch int ret;
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen if (iter->quota == NULL)
8bf42ce5ef783b96a2ded67524173e95e9b45adaTimo Sirainen return NULL;
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen roots = array_get(&iter->quota->roots, &count);
9a382894724292e2af60ef94fc471d761f45e5d5Timo Sirainen if (iter->i >= count)
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen return NULL;
3954326e793bdef1e94e0ad781ed6cc7e48beebbTimo Sirainen
661998e2ccd772ad92a9d4a75cb712692a8c94b3Timo Sirainen for (; iter->i < count; iter->i++) {
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (!quota_root_is_visible(roots[iter->i], iter->box, FALSE))
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen continue;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen ret = roots[iter->i]->resource_ret;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen if (ret == -1) {
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen ret = quota_get_resource(roots[iter->i], "",
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen QUOTA_NAME_STORAGE_KILOBYTES,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen &value, &limit);
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen }
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen if (ret == 0) {
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen ret = quota_get_resource(roots[iter->i], "",
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen QUOTA_NAME_MESSAGES,
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen &value, &limit);
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen }
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen roots[iter->i]->resource_ret = ret;
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen if (ret > 0) {
ade5567577dadb0b275c840208d3ad21a9f00a36Timo Sirainen root = roots[iter->i];
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen break;
e3fabe8d0faa9aab7cae2d0eee9653f581a3061dTimo Sirainen }
}
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;
}
int quota_get_resource(struct quota_root *root, const char *mailbox_name,
const char *name, uint64_t *value_r, uint64_t *limit_r)
{
uint64_t bytes_limit, count_limit;
bool ignored, kilobytes = FALSE;
int 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);
if (ret <= 0)
return ret;
if (quota_root_get_rule_limits(root, mailbox_name,
&bytes_limit, &count_limit,
&ignored) < 0)
return -1;
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 ? 0 : 1;
}
int quota_set_resource(struct quota_root *root, const char *name,
uint64_t value, const char **error_r)
{
struct dict_transaction_context *trans;
const char *key, *error;
if (root->set->limit_set == NULL) {
*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 {
*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.username = root->quota->user->username;
set.base_dir = root->quota->user->set->base_dir;
if (mail_user_get_home(root->quota->user, &set.home_dir) <= 0)
set.home_dir = NULL;
if (dict_init(root->set->limit_set, &set,
&root->limit_set_dict, error_r) < 0)
return -1;
}
trans = dict_transaction_begin(root->limit_set_dict);
key = t_strdup_printf(QUOTA_LIMIT_SET_PATH"%s", key);
dict_set(trans, key, dec2str(value));
if (dict_transaction_commit(&trans, &error) < 0) {
i_error("dict_transaction_commit() failed: %s", error);
*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;
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;
ctx->auto_updating = TRUE;
array_foreach(&ctx->quota->roots, rootp) {
if (!(*rootp)->auto_updating)
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)
{
struct quota_root *const *roots;
const char *mailbox_name;
unsigned int i, count;
uint64_t bytes_limit, count_limit, current, limit, diff;
bool use_grace, ignored;
int 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++) {
if (!quota_root_is_visible(roots[i], ctx->box, TRUE))
continue;
if (quota_root_get_rule_limits(roots[i], mailbox_name,
&bytes_limit, &count_limit,
&ignored) < 0) {
ctx->failed = TRUE;
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);
if (ret > 0) {
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 < 0) {
ctx->failed = TRUE;
return -1;
}
}
if (count_limit > 0) {
ret = quota_get_resource(roots[i], mailbox_name,
QUOTA_NAME_MESSAGES,
&current, &limit);
if (ret > 0) {
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 < 0) {
ctx->failed = TRUE;
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,
};
struct program_client *pc;
restrict_access_init(&set.restrict_set);
if (root->quota->set->debug)
i_debug("quota: 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) {
i_error("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;
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) < 0)
return;
if (quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
&count_current, &count_limit) < 0)
return;
if (ctx->bytes_used > 0 && bytes_current < (uint64_t)ctx->bytes_used)
bytes_before = 0;
else
bytes_before = bytes_current - ctx->bytes_used;
if (ctx->count_used > 0 && count_current < (uint64_t)ctx->count_used)
count_before = 0;
else
count_before = 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, FALSE))
continue;
rule = quota_root_rule_find(roots[i]->set,
mailbox_name);
if (rule != NULL && rule->ignore) {
/* mailbox not included in quota */
continue;
}
if (roots[i]->backend.v.update(roots[i], ctx) < 0)
ret = -1;
else if (!ctx->sync_transaction)
array_append(&warn_roots, &roots[i], 1);
}
/* 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) {
if (root->quota->set->debug) {
i_debug("quota: 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) {
if (root->quota->set->debug) {
i_debug("quota: 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;
const char *const *resources;
unsigned int i;
uint64_t value, limit;
bool cur_overquota = FALSE;
bool quota_over_status;
int 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. */
if (root->quota->set->debug) {
i_debug("quota: 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. */
if (root->quota->set->debug) {
i_debug("quota: 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);
if (ret < 0) {
/* can't reliably verify this */
if (root->quota->set->debug) {
i_debug("quota: Quota %s lookup failed - can't verify quota_over_flag",
resources[i]);
}
return;
}
if (root->quota->set->debug) {
i_debug("quota: quota_over_flag check: %s ret=%d value=%llu limit=%llu",
resources[i], ret,
(unsigned long long)value,
(unsigned long long)limit);
}
if (ret > 0 && value >= limit)
cur_overquota = TRUE;
}
if (root->quota->set->debug) {
i_debug("quota: 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(roots[i]->quota->user, name) == NULL)
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);
}
enum quota_alloc_result quota_try_alloc(struct quota_transaction_context *ctx,
struct mail *mail)
{
uoff_t size;
if (quota_transaction_set_limits(ctx) < 0)
return QUOTA_ALLOC_RESULT_TEMPFAIL;
if (ctx->no_quota_updates)
return QUOTA_ALLOC_RESULT_OK;
if (mail_get_physical_size(mail, &size) < 0) {
enum mail_error error;
const char *errstr = mailbox_get_last_internal_error(mail->box, &error);
if (error == 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;
}
i_error("quota: Failed to get mail size (box=%s, uid=%u): %s",
mail->box->vname, mail->uid, errstr);
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
enum quota_alloc_result ret = quota_test_alloc(ctx, size);
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*() 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)
{
if (ctx->failed)
return QUOTA_ALLOC_RESULT_TEMPFAIL;
if (quota_transaction_set_limits(ctx) < 0)
return QUOTA_ALLOC_RESULT_TEMPFAIL;
uoff_t max_size = ctx->quota->set->max_mail_size;
if (max_size > 0 && 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);
}
static enum quota_alloc_result quota_default_test_alloc(
struct quota_transaction_context *ctx, uoff_t size)
{
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, TRUE))
continue;
ret = quota_root_get_rule_limits(roots[i],
mailbox_get_vname(ctx->box),
&bytes_limit, &count_limit,
&ignore);
if (ret < 0)
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)
return QUOTA_ALLOC_RESULT_OVER_QUOTA_LIMIT;
}
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
}
void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
{
uoff_t size;
if (ctx->auto_updating)
return;
if (mail_get_physical_size(mail, &size) == 0)
ctx->bytes_used += size;
ctx->bytes_ceil = ctx->bytes_ceil2;
ctx->count_used++;
}
void quota_free(struct quota_transaction_context *ctx, struct mail *mail)
{
uoff_t size;
if (ctx->auto_updating)
return;
if (mail_get_physical_size(mail, &size) < 0)
quota_recalculate(ctx, QUOTA_RECALCULATE_MISSING_FREES);
else
quota_free_bytes(ctx, size);
}
void quota_free_bytes(struct quota_transaction_context *ctx,
uoff_t physical_size)
{
ctx->bytes_used -= physical_size;
ctx->count_used--;
}
void quota_recalculate(struct quota_transaction_context *ctx,
enum quota_recalculate recalculate)
{
ctx->recalculate = recalculate;
}