/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2021 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
. */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int
mailbox_folder_create (mu_mailbox_t mbox, const char *name,
mu_record_t record)
{
int rc;
mu_url_t url;
if ((rc = mu_url_uplevel (mbox->url, &url)))
{
if (rc == MU_ERR_NOENT)
{
rc = mu_url_dup (mbox->url, &url);
if (rc)
return rc;
}
else
return rc;
}
rc = mu_folder_create_from_record (&mbox->folder, url, record);
if (rc)
mu_url_destroy (&url);
return rc;
}
int
_mailbox_create_from_record (mu_mailbox_t *pmbox,
mu_record_t record,
mu_url_t url,
mu_folder_t folder,
const char *name)
{
int (*m_init) (mu_mailbox_t) = NULL;
mu_record_get_mailbox (record, &m_init);
if (m_init)
{
int status;
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;
mbox->notify_fd = -1;
/* 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)
{
mu_mailbox_destroy (&mbox);
return status;
}
mbox->url = url;
if (folder)
{
folder->ref++; /* FIXME: No ref/unref function for folders */
mbox->folder = folder;
}
else
/* 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;
return status;
}
return ENOSYS;
}
static int
_create_mailbox0 (mu_mailbox_t *pmbox, mu_url_t url, const char *name)
{
mu_record_t record = NULL;
int rc;
rc = mu_registrar_lookup_url (url, MU_FOLDER_ATTRIBUTE_FILE, &record, NULL);
if (rc == 0)
rc = _mailbox_create_from_record (pmbox, record, url, NULL, name);
return rc;
}
static int
_create_mailbox (mu_mailbox_t *pmbox, const char *name)
{
int status;
mu_url_t url;
status = mu_url_create (&url, name);
if (status)
return status;
status = _create_mailbox0 (pmbox, url, name);
if (status)
mu_url_destroy (&url);
return status;
}
/* The Mailbox Factory.
*/
int
mu_mailbox_create (mu_mailbox_t *pmbox, const char *name)
{
if (pmbox == NULL)
return MU_ERR_OUT_PTR_NULL;
return _create_mailbox (pmbox, name);
}
int
mu_mailbox_create_from_url (mu_mailbox_t *pmbox, mu_url_t url)
{
if (pmbox == NULL)
return MU_ERR_OUT_PTR_NULL;
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 = _mailbox_create_from_record (pmbox, record, url, NULL, name);
if (rc)
mu_url_destroy (&url);
return rc;
}
int
mu_mailbox_create_at (mu_mailbox_t *pmbox, mu_folder_t folder,
const char *name)
{
int rc;
mu_url_t url;
const char *oldpath;
rc = mu_url_dup (folder->url, &url);
if (rc)
return rc;
do
{
char *path;
size_t oldlen, len;
mu_record_t record;
rc = mu_url_sget_path (url, &oldpath);
if (rc)
break;
oldlen = strlen (oldpath);
if (oldlen == 0)
{
path = strdup (name);
if (!path)
{
rc = ENOMEM;
break;
}
}
else
{
if (oldpath[oldlen-1] == '/')
oldlen--;
len = oldlen + 1 + strlen (name) + 1;
path = malloc (len);
if (!path)
{
rc = ENOMEM;
break;
}
memcpy (path, oldpath, oldlen);
path[oldlen++] = '/';
strcpy (path + oldlen, name);
}
rc = mu_url_set_path (url, path);
free (path);
if (rc)
break;
rc = mu_registrar_lookup_url (url, MU_FOLDER_ATTRIBUTE_FILE,
&record, NULL);
if (rc)
break;
rc = _mailbox_create_from_record (pmbox, record, url, folder, name);
}
while (0);
if (rc)
mu_url_destroy (&url);
return rc;
}
void
mu_mailbox_destroy (mu_mailbox_t *pmbox)
{
if (pmbox && *pmbox)
{
mu_mailbox_t mbox = *pmbox;
mu_monitor_t monitor = mbox->monitor;
/* Notify the observers. */
if (mbox->observable)
{
mu_observable_notify (mbox->observable, MU_EVT_MAILBOX_DESTROY,
mbox);
mu_observable_destroy (&mbox->observable, mbox);
}
/* Call the concrete mailbox _destroy method. So it can clean itself. */
if (mbox->_destroy)
mbox->_destroy (mbox);
mu_monitor_wrlock (monitor);
/* Close the stream and nuke it */
if (mbox->stream)
{
/* FIXME: Is this right, should the client be responsible
for closing the stream? */
/* mu_stream_close (mbox->stream); */
mu_stream_destroy (&mbox->stream);
}
mu_url_destroy (&mbox->url);
mu_locker_destroy (&mbox->locker);
mu_folder_destroy (&mbox->folder);
mu_property_destroy (&mbox->property);
free (mbox->notify_user);
free (mbox->notify_sa);
free (mbox);
*pmbox = NULL;
mu_monitor_unlock (monitor);
mu_monitor_destroy (&monitor, mbox);
}
}
/* -------------- stub functions ------------------- */
int
mu_mailbox_open (mu_mailbox_t mbox, int flag)
{
int rc;
if (!mbox)
return EINVAL;
if (mbox->_open == NULL)
return MU_ERR_EMPTY_VFN;
if (mbox->flags & _MU_MAILBOX_OPEN)
return MU_ERR_OPEN;
if (flag & MU_STREAM_QACCESS)
{
/* Quick access mailboxes are read-only */
if (flag & (MU_STREAM_WRITE
| MU_STREAM_APPEND | MU_STREAM_CREAT))
return EACCES;
}
rc = mbox->_open (mbox, flag);
if (rc == 0)
mbox->flags |= _MU_MAILBOX_OPEN;
return rc;
}
int
mu_mailbox_close (mu_mailbox_t mbox)
{
int rc;
if (!mbox)
return EINVAL;
if (!(mbox->flags & _MU_MAILBOX_OPEN))
return MU_ERR_NOT_OPEN;
if (mbox == NULL || mbox->_close == NULL)
return MU_ERR_EMPTY_VFN;
rc = mbox->_close (mbox);
if (rc == 0)
{
if (mbox->notify_fd >= 0)
close (mbox->notify_fd);
mbox->flags &= ~_MU_MAILBOX_OPEN;
}
return rc;
}
int
mu_mailbox_remove (mu_mailbox_t mbox)
{
if (!mbox)
return EINVAL;
if (mbox->flags & _MU_MAILBOX_OPEN)
return MU_ERR_OPEN;
if (mbox->flags & _MU_MAILBOX_REMOVED)
return MU_ERR_MBX_REMOVED;
if (!mbox->_remove)
{
/* Try the owning folder delete method. See comment to mu_folder_delete
in folder.c. This may result in a recursive call to mu_mailbox_remove
which is blocked by setting the _MU_MAILBOX_REMOVED flag. */
int rc;
const char *path;
rc = mu_url_sget_path (mbox->url, &path);
if (rc == 0)
{
mbox->flags |= _MU_MAILBOX_REMOVED;
rc = mu_folder_delete (mbox->folder, path);
if (rc)
mbox->flags &= ~_MU_MAILBOX_REMOVED;
}
return rc;
}
return mbox->_remove (mbox);
}
int
mu_mailbox_flush (mu_mailbox_t mbox, int expunge)
{
if (!mbox)
return EINVAL;
if (mbox->flags & _MU_MAILBOX_REMOVED)
return MU_ERR_MBX_REMOVED;
if (!(mbox->flags & _MU_MAILBOX_OPEN))
return _MU_MAILBOX_OPEN;
if (!(mbox->flags & (MU_STREAM_WRITE|MU_STREAM_APPEND)))
return 0;
if (!(mbox->flags & MU_STREAM_APPEND))
{
size_t i, total = 0;
mu_mailbox_messages_count (mbox, &total);
for (i = 1; i <= total; i++)
{
mu_message_t msg = NULL;
mu_attribute_t attr = NULL;
mu_mailbox_get_message (mbox, i, &msg);
mu_message_get_attribute (msg, &attr);
mu_attribute_set_seen (attr);
}
if (expunge)
return mu_mailbox_expunge (mbox);
}
return mu_mailbox_sync (mbox);
}
#define _MBOX_CHECK_FLAGS(mbox) \
if (mbox == NULL) \
return EINVAL; \
if (mbox->flags & _MU_MAILBOX_REMOVED) \
return MU_ERR_MBX_REMOVED; \
if (!(mbox->flags & _MU_MAILBOX_OPEN)) \
return MU_ERR_NOT_OPEN
#define _MBOX_CHECK(mbox,method) \
_MBOX_CHECK_FLAGS(mbox); \
if (mbox->method == NULL) \
return MU_ERR_EMPTY_VFN
#define _MBOX_CHECK_Q(mbox,method) \
_MBOX_CHECK(mbox,method); \
if (mbox->flags & MU_STREAM_QACCESS) \
return MU_ERR_BADOP
/* messages */
int
mu_mailbox_append_message (mu_mailbox_t mbox, mu_message_t msg)
{
_MBOX_CHECK_Q (mbox, _append_message);
if (!(mbox->flags & (MU_STREAM_WRITE|MU_STREAM_APPEND)))
return EACCES;
return mbox->_append_message (mbox, msg);
}
int
mu_mailbox_get_message (mu_mailbox_t mbox, size_t msgno, mu_message_t *pmsg)
{
_MBOX_CHECK_Q (mbox, _get_message);
return mbox->_get_message (mbox, msgno, pmsg);
}
int
mu_mailbox_quick_get_message (mu_mailbox_t mbox, mu_message_qid_t qid,
mu_message_t *pmsg)
{
_MBOX_CHECK (mbox, _quick_get_message);
if (!(mbox->flags & MU_STREAM_QACCESS))
return MU_ERR_BADOP;
return mbox->_quick_get_message (mbox, qid, pmsg);
}
int
mu_mailbox_messages_count (mu_mailbox_t mbox, size_t *num)
{
_MBOX_CHECK_Q (mbox, _messages_count);
return mbox->_messages_count (mbox, num);
}
int
mu_mailbox_messages_recent (mu_mailbox_t mbox, size_t *num)
{
size_t i, count, n;
int rc;
_MBOX_CHECK_FLAGS (mbox);
if (mbox->flags & MU_STREAM_QACCESS)
return MU_ERR_BADOP;
if (mbox->_messages_recent)
return mbox->_messages_recent (mbox, num);
rc = mu_mailbox_messages_count (mbox, &count);
if (rc)
return rc;
n = 0;
for (i = 1; i < count; i++)
{
mu_message_t msg;
mu_attribute_t attr;
rc = mu_mailbox_get_message (mbox, i, &msg);
if (rc)
return rc;
rc = mu_message_get_attribute (msg, &attr);
if (rc)
return rc;
if (mu_attribute_is_recent (attr))
n++;
}
*num = n;
return 0;
}
int
mu_mailbox_message_unseen (mu_mailbox_t mbox, size_t *num)
{
int rc;
size_t i, count;
_MBOX_CHECK_FLAGS (mbox);
if (mbox->flags & MU_STREAM_QACCESS)
return MU_ERR_BADOP;
if (mbox->_message_unseen)
return mbox->_message_unseen (mbox, num);
rc = mu_mailbox_messages_count (mbox, &count);
if (rc)
return rc;
for (i = 1; i < count; i++)
{
mu_message_t msg;
mu_attribute_t attr;
int rc;
rc = mu_mailbox_get_message (mbox, i, &msg);
if (rc)
return rc;
rc = mu_message_get_attribute (msg, &attr);
if (rc)
return rc;
if (!mu_attribute_is_seen (attr))
{
*num = i;
return 0;
}
}
*num = 0;
return 0;
}
int
mu_mailbox_sync (mu_mailbox_t mbox)
{
_MBOX_CHECK_Q (mbox, _sync);
if (!(mbox->flags & (MU_STREAM_WRITE|MU_STREAM_APPEND)))
return 0;
return mbox->_sync (mbox);
}
/* Historic alias: */
int
mu_mailbox_expunge (mu_mailbox_t mbox)
{
_MBOX_CHECK_Q (mbox, _expunge);
if (!(mbox->flags & (MU_STREAM_WRITE|MU_STREAM_APPEND)))
return EACCES;
return mbox->_expunge (mbox);
}
int
mu_mailbox_is_updated (mu_mailbox_t mbox)
{
if (mbox == NULL ||
!(mbox->flags & _MU_MAILBOX_OPEN) ||
(mbox->flags & _MU_MAILBOX_REMOVED) ||
mbox->_is_updated == NULL)
return 1;
if (mbox->flags & MU_STREAM_QACCESS)
return 1;
return mbox->_is_updated (mbox);
}
int
mu_mailbox_scan (mu_mailbox_t mbox, size_t msgno, size_t *pcount)
{
_MBOX_CHECK_Q (mbox, _scan);
return mbox->_scan (mbox, msgno, pcount);
}
int
mu_mailbox_get_size (mu_mailbox_t mbox, mu_off_t *psize)
{
int status;
_MBOX_CHECK_FLAGS (mbox);
if (mbox->flags & MU_STREAM_QACCESS)
return MU_ERR_BADOP;
if (mbox->_get_size == NULL
|| (status = mbox->_get_size (mbox, psize)) == ENOSYS)
{
/* Fall back to brute-force method */
size_t i, total;
mu_off_t size = 0;
status = mu_mailbox_messages_count (mbox, &total);
if (status)
return status;
for (i = 1; i <= total; i++)
{
mu_message_t msg;
size_t msgsize;
status = mu_mailbox_get_message (mbox, i, &msg);
if (status)
return status;
status = mu_message_size (msg, &msgsize);
if (status)
return status;
size += msgsize;
}
*psize = size;
}
return status;
}
int
mu_mailbox_uidvalidity (mu_mailbox_t mbox, unsigned long *pvalid)
{
_MBOX_CHECK_Q (mbox, _get_uidvalidity);
return mbox->_get_uidvalidity (mbox, pvalid);
}
int
mu_mailbox_uidvalidity_reset (mu_mailbox_t mbox)
{
_MBOX_CHECK_Q (mbox, _set_uidvalidity);
return mbox->_set_uidvalidity (mbox, time (NULL));
}
int
mu_mailbox_uidnext (mu_mailbox_t mbox, size_t *puidnext)
{
_MBOX_CHECK_Q (mbox, _uidnext);
return mbox->_uidnext (mbox, puidnext);
}
/* locking */
int
mu_mailbox_set_locker (mu_mailbox_t mbox, mu_locker_t locker)
{
if (mbox == NULL)
return EINVAL;
if (mbox->locker)
mu_locker_destroy (&mbox->locker);
mbox->locker = locker;
return 0;
}
int
mu_mailbox_get_locker (mu_mailbox_t mbox, mu_locker_t *plocker)
{
if (mbox == NULL)
return EINVAL;
if (plocker == NULL)
return MU_ERR_OUT_PTR_NULL;
*plocker = mbox->locker;
return 0;
}
int
mu_mailbox_get_flags (mu_mailbox_t mbox, int *flags)
{
if (mbox == NULL)
return EINVAL;
if (!flags)
return MU_ERR_OUT_PTR_NULL;
*flags = mbox->flags & ~_MU_MAILBOX_MASK;
return 0;
}
int
mu_mailbox_set_stream (mu_mailbox_t mbox, mu_stream_t stream)
{
if (mbox == NULL)
return EINVAL;
if (mbox->flags & MU_STREAM_QACCESS)
return MU_ERR_BADOP;
if (mbox->stream)
mu_stream_destroy (&mbox->stream);
mbox->stream = stream;
return 0;
}
int
mu_mailbox_get_observable (mu_mailbox_t mbox, mu_observable_t *pobservable)
{
if (mbox == NULL)
return EINVAL;
if (pobservable == NULL)
return MU_ERR_OUT_PTR_NULL;
if (mbox->observable == NULL)
{
int status = mu_observable_create (&mbox->observable, mbox);
if (status != 0)
return status;
}
*pobservable = mbox->observable;
return 0;
}
int
mu_mailbox_set_property (mu_mailbox_t mbox, mu_property_t property)
{
if (mbox == NULL)
return EINVAL;
if (mbox->property)
mu_property_unref (mbox->property);
mbox->property = property;
mu_property_ref (property);
return 0;
}
int
mu_mailbox_get_property (mu_mailbox_t mbox, mu_property_t *pproperty)
{
if (mbox == NULL)
return EINVAL;
if (pproperty == NULL)
return MU_ERR_OUT_PTR_NULL;
if (mbox->property == NULL)
{
int status;
if (mbox->_get_property)
status = mbox->_get_property (mbox, &mbox->property);
else
status = mu_property_create_init (&mbox->property,
mu_assoc_property_init, NULL);
if (status != 0)
return status;
}
*pproperty = mbox->property;
return 0;
}
int
mu_mailbox_get_url (mu_mailbox_t mbox, mu_url_t *purl)
{
if (mbox == NULL)
return EINVAL;
if (purl == NULL)
return MU_ERR_OUT_PTR_NULL;
*purl = mbox->url;
return 0;
}
int
mu_mailbox_get_folder (mu_mailbox_t mbox, mu_folder_t *pfolder)
{
if (mbox == NULL)
return EINVAL;
if (pfolder == NULL)
return MU_ERR_OUT_PTR_NULL;
*pfolder = mbox->folder;
return 0;
}
int
mu_mailbox_set_folder (mu_mailbox_t mbox, mu_folder_t folder)
{
if (mbox == NULL)
return EINVAL;
mbox->folder = folder;
return 0;
}
int
mu_mailbox_lock (mu_mailbox_t mbox)
{
mu_locker_t lock = NULL;
mu_mailbox_get_locker (mbox, &lock);
return mu_locker_lock (lock);
}
int
mu_mailbox_unlock (mu_mailbox_t mbox)
{
mu_locker_t lock = NULL;
mu_mailbox_get_locker (mbox, &lock);
return mu_locker_unlock (lock);
}
int
mu_mailbox_get_uidls (mu_mailbox_t mbox, mu_list_t *plist)
{
mu_list_t list;
int status;
if (mbox == NULL)
return EINVAL;
if (plist == NULL)
return MU_ERR_OUT_PTR_NULL;
status = mu_list_create (&list);
if (status)
return status;
mu_list_set_destroy_item (list, mu_list_free_item);
if (mbox->_get_uidls)
status = mbox->_get_uidls (mbox, list);
else
{
size_t i, total;
status = mu_mailbox_messages_count (mbox, &total);
if (status)
return status;
for (i = 1; i <= total; i++)
{
mu_message_t msg = NULL;
char buf[MU_UIDL_BUFFER_SIZE];
size_t n;
struct mu_uidl *uidl;
status = mu_mailbox_get_message (mbox, i, &msg);
if (status)
break;
status = mu_message_get_uidl (msg, buf, sizeof (buf), &n);
if (status)
break;
uidl = malloc (sizeof (uidl[0]));
if (!uidl)
{
status = ENOMEM;
break;
}
uidl->msgno = i;
strncpy (uidl->uidl, buf, MU_UIDL_BUFFER_SIZE);
status = mu_list_append (list, uidl);
if (status)
{
free (uidl);
break;
}
}
}
*plist = list;
return status;
}
/* Auxiliary function. Performs binary search for a message with the
given UID number */
static int
_uid_bsearch (mu_mailbox_t mbox, size_t start, size_t stop, size_t uid,
size_t *msgno)
{
mu_message_t mid_msg = NULL;
size_t num = 0, middle;
int rc;
middle = (start + stop) / 2;
rc = mu_mailbox_get_message (mbox, middle, &mid_msg);
if (rc)
return rc;
rc = mu_message_get_uid (mid_msg, &num);
if (rc)
return rc;
if (num == uid)
{
*msgno = middle;
return 0;
}
if (start >= stop)
return MU_ERR_NOENT;
if (num > uid)
return _uid_bsearch (mbox, start, middle - 1, uid, msgno);
else /*if (num < seqno)*/
return _uid_bsearch (mbox, middle + 1, stop, uid, msgno);
}
static int
_search_message_uid (mu_mailbox_t mbox, size_t uid, size_t *result)
{
int rc;
size_t num, count;
mu_message_t msg;
rc = mu_mailbox_get_message (mbox, 1, &msg);
if (rc)
return rc;
rc = mu_message_get_uid (msg, &num);
if (rc)
return rc;
if (uid < num)
return MU_ERR_NOENT;
else if (uid == num)
{
*result = 1;
return 0;
}
rc = mu_mailbox_messages_count (mbox, &count);
if (rc)
return rc;
rc = mu_mailbox_get_message (mbox, count, &msg);
if (rc)
return rc;
rc = mu_message_get_uid (msg, &num);
if (rc)
return rc;
if (uid > num)
return MU_ERR_NOENT;
else if (uid == num)
{
*result = count;
return 0;
}
return _uid_bsearch (mbox, 1, count, uid, result);
}
/* Translate message UIDs to message numbers and vice versa. */
int
mu_mailbox_translate (mu_mailbox_t mbox, int cmd, size_t from, size_t *to)
{
int rc = ENOSYS;
mu_message_t msg;
if (mbox == NULL)
return EINVAL;
if (to == NULL)
return MU_ERR_OUT_PTR_NULL;
if (mbox->flags & MU_STREAM_QACCESS)
return MU_ERR_BADOP;
if (mbox->_translate)
rc = mbox->_translate (mbox, cmd, from, to);
if (rc == ENOSYS)
{
switch (cmd)
{
case MU_MAILBOX_UID_TO_MSGNO:
rc = _search_message_uid (mbox, from, to);
break;
case MU_MAILBOX_MSGNO_TO_UID:
rc = mu_mailbox_get_message (mbox, from, &msg);
if (rc)
return rc;
rc = mu_message_get_uid (msg, to);
break;
default:
break;
}
}
return rc;
}
int
mu_mailbox_access_time (mu_mailbox_t mbox, time_t *return_time)
{
_MBOX_CHECK_Q (mbox, _get_atime);
if (!return_time)
return MU_ERR_OUT_PTR_NULL;
return mbox->_get_atime (mbox, return_time);
}