/* 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"
#define DEFAULT_QUOTA_EXCEEDED_MSG \
"Quota exceeded (mailbox for user is full)"
/* 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. */
struct quota_root_iter {
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;
#ifdef HAVE_FS_QUOTA
#endif
};
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(
const char **error_r);
{
return *backend;
}
return NULL;
}
{
}
{
for(unsigned int i = 0; i < array_count("a_backends); i++) {
array_idx("a_backends, i);
return;
}
}
i_unreached();
}
void quota_backends_register(void);
void quota_backends_unregister(void);
void quota_backends_register(void)
{
}
void quota_backends_unregister(void)
{
}
}
struct quota_root_settings *root_set,
const char **error_r)
{
unsigned int i;
for (i = 2;; i++) {
break;
return -1;
}
}
return 0;
}
static int
struct quota_root_settings *root_set,
const char **error_r)
{
unsigned int i;
for (i = 2;; i++) {
break;
rule);
return -1;
}
}
return 0;
}
static int
struct quota_root_settings *root_set,
const char **error_r)
{
return 0;
return -1;
}
return 0;
}
static int
struct quota_root_settings **set_r,
const char **error_r)
{
/* <backend>[:<quota root name>[:<backend args>]] */
if (p == NULL) {
} else {
args = p + 1;
}
return -1;
}
/* save root's name */
if (p == NULL) {
} else {
args = p + 1;
}
} else {
}
i_debug("Quota root: name=%s backend=%s args=%s",
}
return 0;
}
static int
{
return -1;
return -1;
return -1;
return -1;
return -1;
}
return 0;
}
struct quota_transaction_context *qt)
{
switch (res) {
case QUOTA_ALLOC_RESULT_OK:
return "OK";
return "Blocked by an ongoing background quota calculation";
return "Internal quota calculation error";
return "Mail size is larger than the maximum size allowed by "
"server configuration";
}
i_unreached();
}
struct quota_settings **set_r,
const char **error_r)
{
unsigned int i;
"quota_max_mail_size");
&error) < 0) {
error);
return -1;
}
}
i_unreached();
for (i = 2;; i++) {
break;
&error) < 0) {
pool_unref(&pool);
return -1;
}
i_unreached();
}
if (quota_set->max_mail_size == 0 &&
pool_unref(&pool);
return 0;
}
return 1;
}
{
*_quota_set = NULL;
}
{
pool_unref(&pool);
}
const char **error_r)
{
{.param_name = NULL}
};
}
static int
{
sizeof(void *), 10);
return -1;
}
} else {
return -1;
}
return 0;
}
return 1;
}
{
unsigned int i, count;
const char *error;
int ret;
for (i = 0; i < count; i++) {
if (ret < 0) {
return -1;
}
if (ret > 0)
}
return 0;
}
{
unsigned int i, count;
for (i = 0; i < count; i++)
quota_root_deinit(roots[i]);
/* deinit quota roots before setting quser->quota=NULL */
}
const char *mailbox_name,
bool *ignored_r,
const char **error_r)
{
int ret;
const char *error;
"Initializing limits failed for quota backend: %s",
error);
return -1;
}
}
}
/* if default rule limits are 0, user has unlimited quota.
ignore any specific quota rules */
if (bytes_limit != 0 || count_limit != 0) {
&mailbox_name);
ret = 1;
} else {
ret = 0;
}
} else {
bytes_limit = 0;
count_limit = 0;
}
}
return ret;
}
static bool
{
unsigned int i, 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. */
continue;
/* duplicate path */
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 */
return FALSE;
}
}
return FALSE;
}
{
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. */
return;
/* @UNSAFE: get different backends into one array */
for (i = 0; i < count; i++) {
break;
}
}
}
}
{
unsigned int i, count;
/* no quota for this namespace */
return;
}
for (i = 0; i < count; i++) {
if (namespaces[i] == ns) {
break;
}
}
}
struct quota_root_iter *
{
return iter;
}
struct mail_namespace *ns)
{
/* this check works as long as there is only one storage per list */
return FALSE;
return FALSE;
return FALSE;
} else {
return FALSE;
}
return TRUE;
}
static bool
bool enforce)
{
/* we don't want to include this root in quota enforcing */
return FALSE;
}
return FALSE;
/* a single quota root: don't bother checking further */
return TRUE;
}
}
{
unsigned int count;
return NULL;
return NULL;
continue;
break;
}
iter->i++;
return root;
}
{
}
{
unsigned int i, count;
return NULL;
for (i = 0; i < count; i++) {
return roots[i];
}
return NULL;
}
{
}
{
/* if we haven't checked the quota_over_flag yet, do it now */
}
{
}
enum quota_get_result
const char **error_r)
{
const char *error;
}
/* Get the value first. This call may also update quota limits if
they're defined externally. */
if (ret == QUOTA_GET_RESULT_UNLIMITED)
i_panic("Quota backend %s returned QUOTA_GET_RESULT_UNLIMITED "
"while getting resource %s from box %s",
else if (ret != QUOTA_GET_RESULT_LIMITED) {
return ret;
}
"Failed to get quota root rule limits for mailbox %s: %s",
return QUOTA_GET_RESULT_INTERNAL_ERROR;
}
*limit_r = bytes_limit;
*limit_r = count_limit;
else
*limit_r = 0;
if (kilobytes) {
}
}
{
return -1;
}
key = "storage";
key = "bytes";
key = "messages";
else {
return -1;
}
return -1;
}
*error_r = "Internal quota limit update error";
return -1;
}
return 0;
}
{
const char *mailbox_name;
&mailbox_name);
continue;
/* 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 ||
}
/* ignore quota for dsync */
}
return ctx;
}
enum quota_get_result *error_result_r,
const char **error_r)
{
unsigned int i, count;
if (ctx->limits_set)
return 0;
/* find the lowest quota limits from all roots and use them */
for (i = 0; i < count; i++) {
continue;
"Failed to get quota root rule limits for %s: %s",
return -1;
}
if (!ignored)
if (bytes_limit > 0) {
if (ret == QUOTA_GET_RESULT_LIMITED) {
/* over quota */
ctx->bytes_ceil = 0;
ctx->bytes_ceil2 = 0;
} else {
}
} else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
*error_result_r = ret;
"Failed to get quota resource "
QUOTA_NAME_STORAGE_BYTES" for %s: %s",
return -1;
}
}
if (count_limit > 0) {
if (ret == QUOTA_GET_RESULT_LIMITED) {
/* over quota */
ctx->count_ceil = 0;
} else {
}
} else if (ret <= QUOTA_GET_RESULT_INTERNAL_ERROR) {
*error_result_r = ret;
"Failed to get quota resource "
QUOTA_NAME_MESSAGES" for %s: %s",
return -1;
}
}
}
return 0;
}
{
.client_connect_timeout_msecs = 1000,
};
}
socket_path = args[0];
} else {
scheme = "unix";
}
if (*socket_path != '/' &&
args++;
error);
return;
}
(void)program_client_run(pc);
}
struct quota_root *root)
{
unsigned int i, count;
if (count == 0)
return;
": %s", error);
return;
}
": %s", error);
return;
}
bytes_before = 0;
else
count_before = 0;
else
for (i = 0; i < count; i++) {
if (quota_warning_match(&warnings[i],
&reason)) {
break;
}
}
}
{
unsigned int i, count;
const char *mailbox_name;
int ret = 0;
ret = -1;
(void)mail_namespace_find_unalias(
for (i = 0; i < count; i++) {
continue;
/* mailbox not included in quota */
continue;
}
const char *error;
i_error("Failed to update quota for %s: %s",
ret = -1;
}
else if (!ctx->sync_transaction)
}
/* 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. */
} T_END;
return ret;
}
const char **quota_over_script_r,
const char **quota_over_flag_r,
bool *status_r)
{
if (*quota_over_script_r == NULL) {
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=* */
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. */
return TRUE;
}
{
const char *const *resources;
unsigned int i;
bool quota_over_status;
if (root->quota_over_flag_checked)
return;
/* userdb's quota_over_flag lookup is too old. */
i_debug("quota: quota_over_flag check: "
"Flag lookup time is too old - skipping");
}
return;
}
/* 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. */
i_debug("quota: quota_over_flag check: "
"Session was already hibernated - skipping");
}
return;
}
return;
if (ret == QUOTA_GET_RESULT_INTERNAL_ERROR) {
/* can't reliably verify this */
i_error("quota: Quota %s lookup failed - can't verify quota_over_flag: %s",
return;
}
}
}
i_debug("quota: quota_over_flag=%d(%s) vs currently overquota=%d",
quota_over_status ? 1 : 0,
cur_overquota ? 1 : 0);
}
if (cur_overquota != quota_over_status) {
"quota_over_flag mismatch");
}
}
{
unsigned int i, count;
const char *name;
for (i = 0; i < count; i++) {
}
}
{
}
{
else
}
{
const char *error;
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
if (ctx->no_quota_updates)
return QUOTA_ALLOC_RESULT_OK;
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;
}
"Failed to get mail size (box=%s, uid=%u): %s",
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
if (ret != QUOTA_ALLOC_RESULT_OK)
return ret;
/* with quota_try_alloc() we want to keep track of how many bytes
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. */
return QUOTA_ALLOC_RESULT_OK;
}
{
*error_r = "Quota transaction has failed earlier";
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
return QUOTA_ALLOC_RESULT_TEMPFAIL;
}
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. */
}
const char **error_r)
{
unsigned int i, count;
bool ignore;
int ret;
return QUOTA_ALLOC_RESULT_OK;
/* limit reached. */
for (i = 0; i < count; i++) {
continue;
const char *error;
if (ret < 0) {
"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 */
size);
}
}
return QUOTA_ALLOC_RESULT_OVER_QUOTA;
}
{
if (!ctx->auto_updating) {
}
ctx->count_used++;
}
{
ctx->count_used--;
}
enum quota_recalculate recalculate)
{
}
{
}
static void ignoreunlim_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
{
}
static void noenforcing_param_handler(struct quota_root *_root, const char *param_value ATTR_UNUSED)
{
}
{
}
{
*args += tmp_param_len;
}
else if ((*args)[0] == '\0' ||
(*args)[0] == ':') {
/* 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) {
break;
}
}
}
if (fail_on_unknown) {
"Unknown parameter for backend %s: %s",
return -1;
}
else {
break;
}
}
}
return 0;
}