/* 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); }