Commit fd032c6f fd032c6f6cf30af76f67baff997367cafdacb428 by Sergey Poznyakoff

Mailbox quota support in imap4d: initial implementation.

* imap4d/quota.c: New file.
* imap4d/Makefile.am (imap4d_SOURCES): Add quota.c
* imap4d/append.c (imap4d_append0): Refuse to append if
the quota is exceeded or would be exceeded after completing
the operation.
* imap4d/copy.c (imap4d_copy0): Refuse to copy messages if
the quota is exceeded or would be exceeded after completing
the operation.
Return a meaningful textual description.
Attempt to restore mailbox to its original size if the operation failed.
* imap4d/imap4d.c (imap4d_session_setup0): Call quota_setup.
* imap4d/imap4d.h (quota_setup, quota_check, quota_update): New functions.

* include/mailutils/folder.h (mu_list_response) <format>: New member.
* include/mailutils/mailbox.h (mu_mailbox_create_from_record): New
function.
* include/mailutils/types.hin (MU_FOLDER_ATTRIBUTE_LINK): New flag.
* libproto/mbox/folder.c (list_helper): Do not return symbolic links
unless MU_FOLDER_ATTRIBUTE_LINK is set.
Fill in the resp->format member.
* mailbox/mailbox.c (_mailbox_create_from_record): New static function.
(_create_mailbox0): Rewrite as a wrapper over _mailbox_create_from_record.
(mu_mailbox_create_from_record): New function.
1 parent 7ebda349
......@@ -50,6 +50,7 @@ imap4d_SOURCES = \
noop.c\
parsebuf.c\
preauth.c\
quota.c\
rename.c\
search.c\
select.c\
......
......@@ -71,10 +71,11 @@ imap4d_append0 (mu_mailbox_t mbox, int flags, char *date_time, char *text,
struct tm *tm;
time_t t;
mu_envelope_t env;
size_t size;
if (mu_message_create (&msg, &tm))
return 1;
if (mu_memory_stream_create (&stream, MU_STREAM_RDWR)
|| mu_stream_open (stream))
{
......@@ -94,9 +95,9 @@ imap4d_append0 (mu_mailbox_t mbox, int flags, char *date_time, char *text,
}
}
else
time(&t);
time (&t);
tm = gmtime(&t);
tm = gmtime (&t);
while (*text && mu_isblank (*text))
text++;
......@@ -109,15 +110,40 @@ imap4d_append0 (mu_mailbox_t mbox, int flags, char *date_time, char *text,
mu_envelope_set_date (env, _append_date, msg);
mu_envelope_set_sender (env, _append_sender, msg);
mu_message_set_envelope (msg, env, &tm);
rc = _append_size (msg, &size);
if (rc)
{
mu_diag_output (MU_DIAG_NOTICE,
_("cannot compute size of the message being appended; "
"using estimated value: %s"),
mu_strerror (rc));
/* raw estimate */
size = strlen (text);
}
rc = quota_check (size);
if (rc != RESP_OK)
{
*err_text = rc == RESP_NO ?
"Mailbox quota exceeded" : "Operation failed";
mu_message_destroy (&msg, &tm);
return 1;
}
rc = mu_mailbox_append_message (mbox, msg);
if (rc == 0 && flags)
if (rc == 0)
{
size_t num = 0;
mu_attribute_t attr = NULL;
mu_mailbox_messages_count (mbox, &num);
mu_mailbox_get_message (mbox, num, &msg);
mu_message_get_attribute (msg, &attr);
mu_attribute_set_flags (attr, flags);
if (flags)
{
size_t num = 0;
mu_attribute_t attr = NULL;
mu_mailbox_messages_count (mbox, &num);
mu_mailbox_get_message (mbox, num, &msg);
mu_message_get_attribute (msg, &attr);
mu_attribute_set_flags (attr, flags);
}
/* FIXME: If not INBOX */
quota_update (size);
}
mu_message_destroy (&msg, &tm);
......
......@@ -57,6 +57,153 @@ imap4d_copy (struct imap4d_command *command, imap4d_tokbuf_t tok)
return io_completion_response (command, rc, "%s", text);
}
static int
copy_check_size (mu_mailbox_t mbox, size_t n, size_t *set, mu_off_t *size)
{
int status;
size_t i;
mu_off_t total = 0;
for (i = 0; i < n; i++)
{
mu_message_t msg = NULL;
size_t msgno = set[i];
if (msgno)
{
status = mu_mailbox_get_message (mbox, msgno, &msg);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_message", NULL,
status);
return RESP_BAD;
}
else
{
size_t size;
status = mu_message_size (msg, &size);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_message_size", NULL,
status);
return RESP_BAD;
}
total += size;
}
}
}
*size = total;
return quota_check (total);
}
static int
try_copy (mu_mailbox_t dst, mu_mailbox_t src, size_t n, size_t *set)
{
int result;
size_t i;
mu_off_t total;
result = copy_check_size (src, n, set, &total);
if (result)
return result;
for (i = 0; i < n; i++)
{
mu_message_t msg = NULL;
size_t msgno = set[i];
if (msgno)
{
int status = mu_mailbox_get_message (src, msgno, &msg);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_message", NULL,
status);
return RESP_BAD;
}
status = mu_mailbox_append_message (dst, msg);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_append_message",
NULL,
status);
return RESP_BAD;
}
}
}
quota_update (total);
return RESP_OK;
}
static int
safe_copy (mu_mailbox_t dst, mu_mailbox_t src, size_t n, size_t *set,
char **err_text)
{
size_t nmesg;
int status;
status = mu_mailbox_messages_count (dst, &nmesg);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_messages_count",
NULL, status);
*err_text = "Operation failed";
return RESP_NO;
}
status = try_copy (dst, src, n, set);
if (status)
{
size_t maxmesg;
if (status == RESP_NO)
*err_text = "Mailbox quota exceeded";
else
*err_text = "Operation failed";
/* If the COPY command is unsuccessful for any reason, server
implementations MUST restore the destination mailbox to its state
before the COPY attempt. */
status = mu_mailbox_messages_count (dst, &maxmesg);
if (status)
{
mu_url_t url = NULL;
mu_mailbox_get_url (dst, &url);
mu_error (_("cannot count messages in mailbox %s: %s"),
mu_url_to_string (url), mu_strerror (status));
imap4d_bye (ERR_MAILBOX_CORRUPTED);
}
for (nmesg++; nmesg <= maxmesg; nmesg++)
{
mu_message_t msg;
if (mu_mailbox_get_message (dst, nmesg, &msg) == 0)
{
mu_attribute_t attr;
mu_message_get_attribute (msg, &attr);
mu_attribute_set_userflag (attr, MU_ATTRIBUTE_DELETED);
}
}
status = mu_mailbox_flush (dst, 1);
if (status)
{
mu_url_t url = NULL;
mu_mailbox_get_url (dst, &url);
mu_error (_("cannot flush mailbox %s: %s"),
mu_url_to_string (url), mu_strerror (status));
imap4d_bye (ERR_MAILBOX_CORRUPTED);
}
return RESP_NO;
}
return RESP_OK;
}
int
imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
{
......@@ -70,7 +217,8 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
mu_mailbox_t cmbox = NULL;
int arg = IMAP4_ARG_1 + !!isuid;
int ns;
*err_text = NULL;
if (imap4d_tokbuf_argc (tok) != arg + 2)
{
*err_text = "Invalid arguments";
......@@ -89,11 +237,19 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
return RESP_OK;
}
if (isuid)
{
int i;
/* Fixup the message set. Perhaps util_msgset should do it itself? */
for (i = 0; i < n; i++)
set[i] = uid_to_msgno (set[i]);
}
mailbox_name = namespace_getfullpath (name, delim, &ns);
if (!mailbox_name)
{
*err_text = "NO Copy failed.";
*err_text = "Copy failed";
return RESP_NO;
}
......@@ -106,14 +262,7 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
status = mu_mailbox_open (cmbox, MU_STREAM_RDWR | mailbox_mode[ns]);
if (status == 0)
{
size_t i;
for (i = 0; i < n; i++)
{
mu_message_t msg = NULL;
size_t msgno = (isuid) ? uid_to_msgno (set[i]) : set[i];
if (msgno && mu_mailbox_get_message (mbox, msgno, &msg) == 0)
mu_mailbox_append_message (cmbox, msg);
}
status = safe_copy (cmbox, mbox, n, set, err_text);
mu_mailbox_close (cmbox);
}
mu_mailbox_destroy (&cmbox);
......@@ -132,6 +281,7 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text)
of the text of the tagged NO response. This gives a hint to the
client that it can attempt a CREATE command and retry the copy if
the CREATE is successful. */
*err_text = "[TRYCREATE] failed";
if (!*err_text)
*err_text = "[TRYCREATE] failed";
return RESP_NO;
}
......
......@@ -352,9 +352,14 @@ imap4d_session_setup0 ()
util_chdir (imap4d_homedir);
namespace_init_session (imap4d_homedir);
mu_diag_output (MU_DIAG_INFO,
_("user `%s' logged in (source: %s)"), auth_data->name,
auth_data->source);
if (auth_data->quota)
quota_setup ();
return 0;
}
......
......@@ -423,7 +423,12 @@ extern void auth_gsasl_init (void);
#else
# define auth_gsasl_init()
#endif
/* Quota support */
void quota_setup (void);
int quota_check (mu_off_t size);
void quota_update (mu_off_t size);
#ifdef __cplusplus
}
#endif
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
2009, 2010 Free Software Foundation, Inc.
GNU Mailutils is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Mailutils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
#include "imap4d.h"
struct imap4d_sizeinfo
{
mu_off_t size;
mu_off_t nfiles;
mu_off_t ndirs;
mu_off_t nerrs;
};
static int
addsize (mu_folder_t folder, struct mu_list_response *resp, void *data)
{
struct imap4d_sizeinfo *si = data;
if (resp->type & MU_FOLDER_ATTRIBUTE_DIRECTORY)
si->ndirs++;
if (resp->type & MU_FOLDER_ATTRIBUTE_FILE)
{
mu_off_t size;
mu_mailbox_t mbox;
int rc;
si->nfiles++;
rc = mu_mailbox_create_from_record (&mbox, resp->format, resp->name);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_create_from_record",
resp->name, rc);
si->nerrs++;
return 0;
}
rc = mu_mailbox_open (mbox, MU_STREAM_READ);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_open",
resp->name, rc);
si->nerrs++;
mu_mailbox_destroy (&mbox);
return 0;
}
rc = mu_mailbox_get_size (mbox, &size);
mu_mailbox_destroy (&mbox);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_open",
resp->name, rc);
si->nerrs++;
return 0;
}
si->size += size;
}
return 0;
}
void
directory_size (const char *dirname, mu_off_t *size)
{
mu_folder_t folder;
struct imap4d_sizeinfo sizeinfo;
int status;
status = mu_folder_create (&folder, dirname);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_create", dirname, status);
return;
}
status = mu_folder_open (folder, MU_STREAM_READ);
if (status)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_open", dirname, status);
mu_folder_destroy (&folder);
return;
}
memset (&sizeinfo, 0, sizeof (sizeinfo));
status = mu_folder_enumerate (folder, NULL, "*", 0, 0, NULL,
addsize, &sizeinfo);
if (status)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_enumerate", dirname, status);
else
{
mu_diag_output (MU_DIAG_INFO,
_("%s statistics: size=%lu, ndirs=%lu, nfiles=%lu, nerrs=%lu"),
dirname,
(unsigned long)sizeinfo.size,
(unsigned long)sizeinfo.ndirs,
(unsigned long)sizeinfo.nfiles,
(unsigned long)sizeinfo.nerrs);
}
*size = sizeinfo.size;
}
mu_off_t used_size;
void
quota_setup ()
{
directory_size (imap4d_homedir, &used_size);
}
int
quota_check (mu_off_t size)
{
char *mailbox_name;
mu_mailbox_t mbox;
mu_off_t total;
int rc;
if (auth_data->quota == 0)
return RESP_OK;
total = used_size;
mailbox_name = namespace_getfullpath ("INBOX", "/", NULL);
rc = mu_mailbox_create (&mbox, mailbox_name);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_create", mailbox_name, rc);
free (mailbox_name);
}
else
{
do
{
mu_off_t mbsize;
rc = mu_mailbox_open (mbox, MU_STREAM_READ);
if (rc)
{
if (rc != ENOENT)
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_open",
mailbox_name, rc);
break;
}
rc = mu_mailbox_get_size (mbox, &mbsize);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_size",
mailbox_name, rc);
mu_mailbox_close (mbox);
break;
}
total += mbsize;
mu_mailbox_close (mbox);
}
while (0);
mu_mailbox_destroy (&mbox);
}
free (mailbox_name);
if (rc)
return RESP_BAD;
if (total > auth_data->quota)
{
mu_diag_output (MU_DIAG_NOTICE,
_("user %s is out of mailbox quota"),
auth_data->name);
return RESP_NO;
}
else if (total + size > auth_data->quota)
{
mu_diag_output (MU_DIAG_NOTICE,
_("user %s: adding %lu bytes would exceed mailbox quota"),
auth_data->name, (unsigned long) size);
return RESP_NO;
}
return RESP_OK;
}
void
quota_update (mu_off_t size)
{
used_size += size;
}
......@@ -32,6 +32,7 @@ struct mu_list_response
int level;
int separator;
char *name;
mu_record_t format; /* Associated mailbox format record */
};
typedef int (*mu_folder_match_fp) (const char *, void *, int);
......
......@@ -40,9 +40,12 @@ int mu_construct_user_mailbox_url (char **pout, const char *name);
/* Constructor/destructor and possible types. */
extern int mu_mailbox_create (mu_mailbox_t *, const char *);
extern int mu_mailbox_create_from_url (mu_mailbox_t *, mu_url_t);
extern int mu_mailbox_create_from_record (mu_mailbox_t *pmbox,
mu_record_t record,
const char *name);
extern int mu_mailbox_create_default (mu_mailbox_t *, const char *);
extern void mu_mailbox_destroy (mu_mailbox_t *);
extern int mu_mailbox_create_default (mu_mailbox_t *, const char *);
extern int mu_mailbox_open (mu_mailbox_t, int flag);
extern int mu_mailbox_close (mu_mailbox_t);
......
......@@ -118,8 +118,9 @@ typedef struct _mu_progmailer *mu_progmailer_t;
typedef struct _mu_secret *mu_secret_t;
typedef struct _mu_mime_io_buffer *mu_mime_io_buffer_t;
#define MU_FOLDER_ATTRIBUTE_DIRECTORY 0x001
#define MU_FOLDER_ATTRIBUTE_FILE 0x002
#define MU_FOLDER_ATTRIBUTE_DIRECTORY 0x001
#define MU_FOLDER_ATTRIBUTE_FILE 0x002
#define MU_FOLDER_ATTRIBUTE_LINK 0x004
#define MU_FOLDER_ATTRIBUTE_ALL \
(MU_FOLDER_ATTRIBUTE_DIRECTORY|MU_FOLDER_ATTRIBUTE_FILE)
......
......@@ -315,7 +315,7 @@ list_helper (struct search_data *data, mu_record_t record,
DIR *dirp;
struct dirent *dp;
int stop = 0;
if (data->max_level && level > data->max_level)
return 0;
......@@ -345,13 +345,15 @@ list_helper (struct search_data *data, mu_record_t record,
if (ename[ename[0] != '.' ? 0 : ename[1] != '.' ? 1 : 2] == 0)
continue;
fname = get_pathname (dirname, ename);
if (stat (fname, &st) == 0)
if (lstat (fname, &st) == 0)
{
int f;
if (S_ISDIR (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_DIRECTORY;
else if (S_ISREG (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_FILE;
else if (S_ISLNK (st.st_mode))
f = MU_FOLDER_ATTRIBUTE_LINK;
else
f = 0;
if (mu_record_list_p (record, ename, f))
......@@ -386,6 +388,7 @@ list_helper (struct search_data *data, mu_record_t record,
resp->level = level;
resp->separator = '/';
resp->type = type;
resp->format = rec;
if (resp->type == 0)
{
......
......@@ -69,102 +69,110 @@ mailbox_folder_create (mu_mailbox_t mbox, const char *name,
return rc;
}
static int
_create_mailbox0 (mu_mailbox_t *pmbox, mu_url_t url, const char *name)
int
_mailbox_create_from_record (mu_mailbox_t *pmbox,
mu_record_t record,
mu_url_t url,
const char *name)
{
int status;
mu_record_t record = NULL;
if (mu_registrar_lookup_url (url, MU_FOLDER_ATTRIBUTE_FILE, &record, NULL)
== 0)
mu_log_level_t level;
int (*m_init) (mu_mailbox_t) = NULL;
mu_record_get_mailbox (record, &m_init);
if (m_init)
{
mu_log_level_t level;
int (*m_init) (mu_mailbox_t) = NULL;
int status;
int (*u_init) (mu_url_t) = NULL;
mu_mailbox_t mbox;
mu_record_get_mailbox (record, &m_init);
if (m_init)
{
int (*u_init) (mu_url_t) = NULL;
mu_mailbox_t mbox;
/* Allocate memory for mbox. */
mbox = calloc (1, sizeof (*mbox));
if (mbox == NULL)
return ENOMEM;
/* Initialize the internal lock now, so the concrete mailbox
could use it. */
status = mu_monitor_create (&mbox->monitor, 0, mbox);
if (status != 0)
{
mu_mailbox_destroy (&mbox);
return status;
}
/* Make sure scheme contains actual mailbox scheme */
/* FIXME: It is appropriate not for all record types. For now we
assume that if the record scheme ends with a plus sign, this
should not be done. Probably it requires some flag in struct
_mu_record? */
if (strcmp (url->scheme, record->scheme))
{
char *p = strdup (record->scheme);
if (!p)
{
mu_mailbox_destroy (&mbox);
return errno;
}
free (url->scheme);
url->scheme = p;
}
mu_record_get_url (record, &u_init);
if (u_init && (status = u_init (url)) != 0)
/* Allocate memory for mbox. */
mbox = calloc (1, sizeof (*mbox));
if (mbox == NULL)
return ENOMEM;
/* Initialize the internal lock now, so the concrete mailbox
could use it. */
status = mu_monitor_create (&mbox->monitor, 0, mbox);
if (status != 0)
{
mu_mailbox_destroy (&mbox);
return status;
}
/* Make sure scheme contains actual mailbox scheme */
/* FIXME: It is appropriate not for all record types. For now we
assume that if the record scheme ends with a plus sign, this
should not be done. Probably it requires some flag in struct
_mu_record? */
if (strcmp (url->scheme, record->scheme))
{
char *p = strdup (record->scheme);
if (!p)
{
mu_mailbox_destroy (&mbox);
return status;
return errno;
}
mbox->url = url;
/* Create the folder before initializing the concrete mailbox.
The mailbox needs it's back pointer. */
status = mailbox_folder_create (mbox, name, record);
free (url->scheme);
url->scheme = p;
}
mu_record_get_url (record, &u_init);
if (u_init && (status = u_init (url)) != 0)
{
mu_mailbox_destroy (&mbox);
return status;
}
mbox->url = url;
/* Create the folder before initializing the concrete mailbox.
The mailbox needs it's back pointer. */
status = mailbox_folder_create (mbox, name, record);
if (status == 0)
status = m_init (mbox); /* Create the concrete mailbox type. */
if (status != 0)
{
/* Take care not to destroy url. Leave it to caller. */
mbox->url = NULL;
mu_mailbox_destroy (&mbox);
}
else
{
*pmbox = mbox;
if (status == 0)
status = m_init (mbox); /* Create the concrete mailbox type. */
if (status != 0)
{
/* Take care not to destroy url. Leave it to caller. */
mbox->url = NULL;
mu_mailbox_destroy (&mbox);
}
else
level = mu_global_debug_level ("mailbox");
if (level)
{
*pmbox = mbox;
level = mu_global_debug_level ("mailbox");
if (level)
{
int status = mu_debug_create (&mbox->debug, mbox);
if (status)
return 0; /* FIXME: don't want to bail out just because I
failed to create a *debug* object. But I may
be wrong... */
mu_debug_set_level (mbox->debug, level);
if (level & MU_DEBUG_INHERIT)
mu_folder_set_debug (mbox->folder, mbox->debug);
}
int status = mu_debug_create (&mbox->debug, mbox);
if (status)
return 0; /* FIXME: don't want to bail out just because I
failed to create a *debug* object. But I may
be wrong... */
mu_debug_set_level (mbox->debug, level);
if (level & MU_DEBUG_INHERIT)
mu_folder_set_debug (mbox->folder, mbox->debug);
}
return status;
}
return status;
}
return MU_ERR_NO_HANDLER;
}
static int
_create_mailbox0 (mu_mailbox_t *pmbox, mu_url_t url, const char *name)
{
mu_record_t record = NULL;
if (mu_registrar_lookup_url (url, MU_FOLDER_ATTRIBUTE_FILE, &record, NULL)
== 0)
return _mailbox_create_from_record (pmbox, record, url, name);
return MU_ERR_NO_HANDLER;
}
static int
_create_mailbox (mu_mailbox_t *pmbox, const char *name)
{
int status;
......@@ -202,6 +210,24 @@ mu_mailbox_create_from_url (mu_mailbox_t *pmbox, mu_url_t url)
return _create_mailbox0 (pmbox, url, mu_url_to_string (url));
}
int
mu_mailbox_create_from_record (mu_mailbox_t *pmbox, mu_record_t record,
const char *name)
{
mu_url_t url;
int rc;
rc = mu_url_create (&url, name);
if (rc)
return rc;
rc = mu_url_parse (url);
if (rc == 0)
rc = _mailbox_create_from_record (pmbox, record, url, name);
if (rc)
mu_url_destroy (&url);
return rc;
}
void
mu_mailbox_destroy (mu_mailbox_t *pmbox)
{
......