summaryrefslogtreecommitdiff
path: root/mailbox/mbox/mbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'mailbox/mbox/mbox.c')
-rw-r--r--mailbox/mbox/mbox.c1753
1 files changed, 1753 insertions, 0 deletions
diff --git a/mailbox/mbox/mbox.c b/mailbox/mbox/mbox.c
new file mode 100644
index 000000000..f35571982
--- /dev/null
+++ b/mailbox/mbox/mbox.c
@@ -0,0 +1,1753 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/* First draft by Alain Magloire */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <mbox0.h>
+
+#define ATTRIBUTE_IS_DELETED(flag) (flag & MU_ATTRIBUTE_DELETED)
+#define ATTRIBUTE_IS_EQUAL(flag1, flag2) (flag1 == flag2)
+
+static void mbox_destroy (mailbox_t);
+
+/* Below are the headers field-names that we are caching for speed, it is
+ more or less the list of headers in ENVELOPE command from IMAP.
+
+ NOTE: These are indexed with H_.* macros defined in mbox0.h. Keep them
+ in sync if you add or remove something. */
+
+const char *fhdr_table[HDRSIZE] =
+{
+ "Bcc",
+ "Cc",
+ "Content-Language",
+ "Content-Transfer-Encoding",
+ "Content-Type",
+ "Date",
+ "From",
+ "In-Reply-To",
+ "Message-ID",
+ "Reference",
+ "Reply-To",
+ "Sender",
+ "Subject",
+ "To",
+ "X-UIDL"
+};
+
+/* Mailbox concrete implementation. */
+static int mbox_open __P ((mailbox_t, int));
+static int mbox_close __P ((mailbox_t));
+static int mbox_get_message __P ((mailbox_t, size_t, message_t *));
+/* static int mbox_get_message_by_uid __P ((mailbox_t, size_t, message_t *)); */
+static int mbox_append_message __P ((mailbox_t, message_t));
+static int mbox_messages_count __P ((mailbox_t, size_t *));
+static int mbox_messages_recent __P ((mailbox_t, size_t *));
+static int mbox_message_unseen __P ((mailbox_t, size_t *));
+static int mbox_expunge0 __P ((mailbox_t, int));
+static int mbox_expunge __P ((mailbox_t));
+static int mbox_save_attributes __P ((mailbox_t));
+static int mbox_uidvalidity __P ((mailbox_t, unsigned long *));
+static int mbox_uidnext __P ((mailbox_t, size_t *));
+static int mbox_scan __P ((mailbox_t, size_t, size_t *));
+static int mbox_is_updated __P ((mailbox_t));
+static int mbox_get_size __P ((mailbox_t, off_t *));
+
+/* private stuff */
+static int mbox_append_message0 __P ((mailbox_t, message_t, off_t *,
+ int, int));
+static int mbox_message_uid __P ((message_t, size_t *));
+static int mbox_header_fill __P ((header_t, char *, size_t, off_t,
+ size_t *));
+static int mbox_get_body_fd __P ((stream_t, int *));
+static int mbox_get_fd __P ((mbox_message_t, int *));
+static int mbox_get_attr_flags __P ((attribute_t, int *));
+static int mbox_set_attr_flags __P ((attribute_t, int));
+static int mbox_unset_attr_flags __P ((attribute_t, int));
+static int mbox_body_read __P ((stream_t, char *, size_t, off_t,
+ size_t *));
+static int mbox_body_readline __P ((stream_t, char *, size_t, off_t,
+ size_t *));
+static int mbox_readstream __P ((mbox_message_t, char *, size_t,
+ off_t, size_t *, int, off_t,
+ off_t));
+static int mbox_stream_size __P((stream_t stream, off_t *psize));
+
+static int mbox_header_size __P ((header_t, size_t *));
+static int mbox_header_lines __P ((header_t, size_t *));
+static int mbox_body_size __P ((body_t, size_t *));
+static int mbox_body_lines __P ((body_t, size_t *));
+static int mbox_envelope_sender __P ((envelope_t, char *, size_t,
+ size_t *));
+static int mbox_envelope_date __P ((envelope_t, char *, size_t,
+ size_t *));
+static int mbox_tmpfile __P ((mailbox_t, char **pbox));
+
+/* Allocate the mbox_data_t struct(concrete mailbox), but don't do any
+ parsing on the name or even test for existence. However we do strip any
+ leading "mbox:" part of the name, this is suppose to be the
+ protocol/scheme name. */
+int
+_mailbox_mbox_init (mailbox_t mailbox)
+{
+ mbox_data_t mud;
+ size_t name_len;
+
+ if (mailbox == NULL)
+ return EINVAL;
+
+ /* Allocate specific mbox data. */
+ mud = mailbox->data = calloc (1, sizeof (*mud));
+ if (mailbox->data == NULL)
+ return ENOMEM;
+
+ /* Back pointer. */
+ mud->mailbox = mailbox;
+
+ /* Copy the name:
+ We do not do any further interpretation after the scheme "mbox:"
+ Because for example on distributed system like QnX4 a file name is
+ //390/etc/passwd. So the best approach is to let the OS handle it
+ for example if we receive: "mbox:///var/mail/alain" the mailbox name
+ will be "///var/mail/alain", we let open() do the right thing.
+ So it will let things like this "mbox://390/var/mail/alain" where
+ the "//" _is_ part of the filename, pass correctely. */
+ url_get_path (mailbox->url, NULL, 0, &name_len);
+ mud->name = calloc (name_len + 1, sizeof (char));
+ if (mud->name == NULL)
+ {
+ free (mud);
+ mailbox->data = NULL;
+ return ENOMEM;
+ }
+ url_get_path (mailbox->url, mud->name, name_len + 1, NULL);
+
+ mud->state = MBOX_NO_STATE;
+
+ /* Overloading the defaults. */
+ mailbox->_destroy = mbox_destroy;
+
+ mailbox->_open = mbox_open;
+ mailbox->_close = mbox_close;
+
+ /* Overloading of the entire mailbox object methods. */
+ mailbox->_get_message = mbox_get_message;
+ mailbox->_append_message = mbox_append_message;
+ mailbox->_messages_count = mbox_messages_count;
+ mailbox->_messages_recent = mbox_messages_recent;
+ mailbox->_message_unseen = mbox_message_unseen;
+ mailbox->_expunge = mbox_expunge;
+ mailbox->_save_attributes = mbox_save_attributes;
+ mailbox->_uidvalidity = mbox_uidvalidity;
+ mailbox->_uidnext = mbox_uidnext;
+
+ mailbox->_scan = mbox_scan;
+ mailbox->_is_updated = mbox_is_updated;
+
+ mailbox->_get_size = mbox_get_size;
+
+ /* Set our properties. */
+ {
+ property_t property = NULL;
+ mailbox_get_property (mailbox, &property);
+ property_set_value (property, "TYPE", "MBOX", 1);
+ }
+
+ MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_init(%s)\n", mud->name);
+ return 0; /* okdoke */
+}
+
+/* Free all ressources associated with Unix concrete mailbox. */
+static void
+mbox_destroy (mailbox_t mailbox)
+{
+ if (mailbox->data)
+ {
+ size_t i;
+ mbox_data_t mud = mailbox->data;
+ MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE,
+ "mbox_destroy (%s)\n", mud->name);
+ monitor_wrlock (mailbox->monitor);
+ for (i = 0; i < mud->umessages_count; i++)
+ {
+ mbox_message_t mum = mud->umessages[i];
+ if (mum)
+ {
+ size_t j;
+ message_destroy (&(mum->message), mum);
+ for (j = 0; j < HDRSIZE; j++)
+ if (mum->fhdr[j])
+ free (mum->fhdr[j]);
+ free (mum);
+ }
+ }
+ if (mud->umessages)
+ free (mud->umessages);
+ if (mud->name)
+ free (mud->name);
+ free (mud);
+ mailbox->data = NULL;
+ monitor_unlock (mailbox->monitor);
+ }
+}
+
+/* Open the file. For MU_STREAM_READ, the code tries mmap() first and fall
+ back to normal file. */
+static int
+mbox_open (mailbox_t mailbox, int flags)
+{
+ mbox_data_t mud = mailbox->data;
+ int status = 0;
+
+ if (mud == NULL)
+ return EINVAL;
+
+ mailbox->flags = flags;
+
+ /* Get a stream. */
+ if (mailbox->stream == NULL)
+ {
+ /* We do not try to mmap for CREAT or APPEND, it is not supported. */
+ status = (flags & MU_STREAM_CREAT)
+ || (mailbox->flags & MU_STREAM_APPEND);
+
+ /* Try to mmap () the file first. */
+ if (status == 0)
+ {
+ status = mapfile_stream_create (&mailbox->stream, mud->name, mailbox->flags);
+ if (status == 0)
+ {
+ status = stream_open (mailbox->stream);
+ }
+ }
+
+ /* Fall back to normal file if mmap() failed. */
+ if (status != 0)
+ {
+ status = file_stream_create (&mailbox->stream, mud->name, mailbox->flags);
+ if (status != 0)
+ return status;
+ status = stream_open (mailbox->stream);
+ }
+ /* All failed, bail out. */
+ if (status != 0)
+ {
+ stream_destroy (&mailbox->stream, NULL);
+ return status;
+ }
+ /* Even on top, of normal FILE *, lets agressively cache. But this
+ may not be suitable for system tight on memory. */
+ stream_setbufsiz (mailbox->stream, BUFSIZ);
+ }
+ else
+ {
+ status = stream_open (mailbox->stream);
+ if (status != 0)
+ return status;
+ }
+
+ MAILBOX_DEBUG2 (mailbox, MU_DEBUG_TRACE, "mbox_open(%s, 0x%x)\n",
+ mud->name, mailbox->flags);
+
+ if (mailbox->locker == NULL)
+ status = locker_create (&(mailbox->locker), mud->name, 0);
+ return status;
+}
+
+static int
+mbox_close (mailbox_t mailbox)
+{
+ mbox_data_t mud = mailbox->data;
+ /* size_t i; */
+
+ if (mud == NULL)
+ return EINVAL;
+
+ MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_close(%s)\n", mud->name);
+
+ /* Make sure that we do not hold any file locking. */
+ locker_unlock (mailbox->locker);
+
+#if 0
+ /* RFC: I'm not sure on the right approach especially if the client is
+ working in disconnected mode, where it can mailbox_close/mailbox_open
+ for each request, maybe we should keep them for a while. */
+ monitor_wrlock (mailbox->monitor);
+ /* Before closing we need to remove all the messages
+ - to reclaim the memory
+ - to prepare for another scan. */
+ for (i = 0; i < mud->umessages_count; i++)
+ {
+ mbox_message_t mum = mud->umessages[i];
+ /* Destroy the attach messages. */
+ if (mum)
+ {
+ size_t j;
+ message_destroy (&(mum->message), mum);
+ for (j = 0; j < HDRSIZE; j++)
+ if (mum->fhdr[j])
+ free (mum->fhdr[j]);
+ free (mum);
+ }
+ }
+ if (mud->umessages)
+ free (mud->umessages);
+ mud->umessages = NULL;
+ mud->messages_count = mud->umessages_count = 0;
+ mud->size = 0;
+ mud->uidvalidity = 0;
+ mud->uidnext = 0;
+ monitor_unlock (mailbox->monitor);
+#endif
+ return stream_close (mailbox->stream);
+}
+
+/* Cover function that call the real thing, mbox_scan(), with
+ notification set. */
+static int
+mbox_scan (mailbox_t mailbox, size_t msgno, size_t *pcount)
+{
+ size_t i;
+ mbox_data_t mud = mailbox->data;
+ MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_scan(%s)\n", mud->name);
+ if (! mbox_is_updated (mailbox))
+ return mbox_scan0 (mailbox, msgno, pcount, 1);
+ /* Since the mailbox is already updated fake the scan. */
+ if (msgno > 0)
+ msgno--; /* The fist message is number "1", decremente for the C array. */
+ for (i = msgno; i < mud->messages_count; i++)
+ {
+ if (observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD) != 0)
+ break;
+ if (((i +1) % 50) == 0)
+ {
+ observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS);
+ }
+ }
+ return 0;
+}
+
+/* FIXME: How to handle a shrink ? meaning, the &^$^@%#@^& user start two
+ browsers and deleted emails in one session. My views is that we should
+ scream bloody murder and hunt them with a machette. But for now just play
+ dumb, but maybe the best approach is to pack our things and leave
+ .i.e exit()/abort(). */
+static int
+mbox_is_updated (mailbox_t mailbox)
+{
+ off_t size = 0;
+ mbox_data_t mud = mailbox->data;
+ if (stream_size (mailbox->stream, &size) != 0)
+ return 0;
+ if (size < mud->size)
+ {
+ observable_notify (mailbox->observable, MU_EVT_MAILBOX_CORRUPT);
+ /* And be verbose. ? */
+ mu_error ("* BAD : Mailbox corrupted, shrank size\n");
+ /* FIXME: should I crash. */
+ return 0;
+ }
+ return (mud->size == size);
+}
+
+/* Try to create an uniq file, we no race conditions. */
+static int
+mbox_tmpfile (mailbox_t mailbox, char **pbox)
+{
+ const char *tmpdir;
+ int fd;
+ const char *basename;
+ mbox_data_t mud = mailbox->data;
+
+ /* P_tmpdir should be in <stdio.h>. */
+#ifndef P_tmpdir
+# define P_tmpdir "/tmp"
+#endif
+
+ basename = strrchr (mud->name, '/');
+ if (basename)
+ basename++;
+ else
+ basename = mud->name;
+
+ tmpdir = getenv ("TMPDIR") ? getenv ("TMPDIR") : P_tmpdir;
+ /* (separator + null) == 2 + XXXXXX == 6 + ... */
+ *pbox = calloc (strlen (tmpdir) + /* '/' */ 1 + /*strlen ("MU_")*/ 3 +
+ strlen (basename) + /*strlen ("_XXXXXX")*/ 7 + /*null*/1,
+ sizeof (**pbox));
+ if (*pbox == NULL)
+ return -1;
+ sprintf (*pbox, "%s/MU_%s_XXXXXX", tmpdir, basename);
+#ifdef HAVE_MKSTEMP
+ fd = mkstemp (*pbox);
+#else
+ /* Create the file. It must not exist. If it does exist, fail. */
+ if (mktemp (*pbox))
+ {
+ fd = open (*pbox, O_RDWR|O_CREAT|O_EXCL, 0600);
+ }
+ else
+ {
+ free (*pbox);
+ fd = -1;
+ }
+#endif
+ return fd;
+}
+
+/* For the expunge bits we took a very cautionnary approach, meaning
+ we create a temporary mailbox in the tmpdir copy all the message not mark
+ deleted(Actually we copy all the message that may have been modified
+ i.e new header values set; UIDL or UID or etc ....
+ and skip the deleted ones, truncate the real mailbox to the desired size
+ and overwrite with the tmp mailbox. The approach to do everyting
+ in core is tempting but require
+ - to much memory, it is not rare nowadays to have 30 Megs mailbox,
+ - also there is danger for filesystems with quotas,
+ - if we run out of disk space everything is lost.
+ - or some program may not respect the advisory lock and decide to append
+ a new message while your expunging etc ...
+ The real downside to the approach is that when things go wrong
+ the temporary file may be left in /tmp, which is not all that bad
+ because at least, we have something to recuperate when failure. */
+static int
+mbox_expunge0 (mailbox_t mailbox, int remove_deleted)
+{
+ mbox_data_t mud = mailbox->data;
+ mbox_message_t mum;
+ int status = 0;
+ sigset_t signalset;
+ int tempfile;
+ size_t i, j, dirty; /* dirty will indicate the first modified message. */
+ off_t marker = 0; /* marker will be the position to truncate. */
+ off_t total = 0;
+ char *tmpmboxname = NULL;
+ mailbox_t tmpmailbox = NULL;
+ size_t save_imapbase = 0; /* uidvalidity is save in the first message. */
+#ifdef WITH_PTHREAD
+ int state;
+#endif
+
+ if (mud == NULL)
+ return EINVAL;
+
+ MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_expunge (%s)\n", mud->name);
+
+ /* Noop. */
+ if (mud->messages_count == 0)
+ return 0;
+
+ /* Find the first dirty(modified) message. */
+ for (dirty = 0; dirty < mud->messages_count; dirty++)
+ {
+ mum = mud->umessages[dirty];
+ /* Message may have been tampered, break here. */
+ if ((mum->attr_flags & MU_ATTRIBUTE_MODIFIED) ||
+ (mum->attr_flags & MU_ATTRIBUTE_DELETED) ||
+ (mum->message && message_is_modified (mum->message)))
+ break;
+ }
+
+ /* Did something change ? */
+ if (dirty == mud->messages_count)
+ return 0; /* Nothing change, bail out. */
+
+ /* Create a temporary file. */
+ tempfile = mbox_tmpfile (mailbox, &tmpmboxname);
+ if (tempfile == -1)
+ {
+ if (tmpmboxname)
+ free (tmpmboxname);
+ mu_error ("Failed to create temporary file when expunging.\n");
+ return errno;
+ }
+
+ /* This is redundant, we go to the loop again. But it's more secure here
+ since we don't want to be disturb when expunging. Destroy all the
+ messages mark for deletion. */
+ if (remove_deleted)
+ {
+ for (j = 0; j < mud->messages_count; j++)
+ {
+ mum = mud->umessages[j];
+ if (mum && mum->message && ATTRIBUTE_IS_DELETED (mum->attr_flags))
+ message_destroy (&(mum->message), mum);
+ }
+ }
+
+ /* Create temporary mailbox_t. */
+ {
+ mbox_data_t tmp_mud;
+ char *m = alloca (5 + strlen (tmpmboxname) + 1);
+ /* Try via the mbox: protocol. */
+ sprintf (m, "mbox:%s", tmpmboxname);
+ status = mailbox_create (&tmpmailbox, m);
+ if (status != 0)
+ {
+ /* Do not give up just yet, maybe they register the path_record. */
+ sprintf (m, "%s", tmpmboxname);
+ status = mailbox_create (&tmpmailbox, m);
+ if (status != 0)
+ {
+ /* Ok give up. */
+ close (tempfile);
+ remove (tmpmboxname);
+ free (tmpmboxname);
+ return status;
+ }
+ }
+
+ /* Must be flag CREATE if not the mailbox_open will try to mmap()
+ the file. */
+ status = mailbox_open (tmpmailbox, MU_STREAM_CREAT | MU_STREAM_RDWR);
+ if (status != 0)
+ {
+ close (tempfile);
+ remove (tmpmboxname);
+ free (tmpmboxname);
+ return status;
+ }
+ close (tempfile); /* This one is useless the mailbox have its own. */
+ tmp_mud = tmpmailbox->data;
+ /* May need when appending. */
+ tmp_mud->uidvalidity = mud->uidvalidity;
+ tmp_mud->uidnext = mud->uidnext;
+ }
+
+ /* Get the File lock. */
+ if ((status = locker_lock (mailbox->locker)) != 0)
+ {
+ mailbox_close (tmpmailbox);
+ mailbox_destroy (&tmpmailbox);
+ remove (tmpmboxname);
+ free (tmpmboxname);
+ mu_error ("Failed to grab the lock: %s\n", mu_strerror(status));
+ return status;
+ }
+
+ /* Critical section, we can not allowed signal here. */
+#ifdef WITH_PTHREAD
+ pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
+#endif
+ sigemptyset (&signalset);
+ sigaddset (&signalset, SIGTERM);
+ sigaddset (&signalset, SIGHUP);
+ sigaddset (&signalset, SIGTSTP);
+ sigaddset (&signalset, SIGINT);
+ sigaddset (&signalset, SIGWINCH);
+ sigprocmask (SIG_BLOCK, &signalset, 0);
+
+ /* Set the marker position. */
+ marker = mud->umessages[dirty]->header_from;
+ total = 0;
+
+ /* Copy to the temporary mailbox emails not mark deleted. */
+ for (i = dirty; i < mud->messages_count; i++)
+ {
+ mum = mud->umessages[i];
+
+ /* Skip it, if mark for deletion. */
+ if (remove_deleted && ATTRIBUTE_IS_DELETED (mum->attr_flags))
+ {
+ /* We save the uidvalidity in the first message, if it is being
+ deleted we need to move the uidvalidity to the first available
+ (non-deleted) message. */
+ if (i == save_imapbase)
+ {
+ save_imapbase = i + 1;
+ if (save_imapbase < mud->messages_count)
+ (mud->umessages[save_imapbase])->attr_flags |= MU_ATTRIBUTE_MODIFIED;
+ }
+ continue;
+ }
+
+ /* Do the expensive mbox_append_message0() only if mark dirty. */
+ if ((mum->attr_flags & MU_ATTRIBUTE_MODIFIED) ||
+ (mum->message && message_is_modified (mum->message)))
+ {
+ /* The message was not instanciated, probably the dirty flag was
+ set by mbox_scan(), create one here. */
+ if (mum->message == 0)
+ {
+ message_t msg;
+ status = mbox_get_message (mailbox, i + 1, &msg);
+ if (status != 0)
+ {
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ }
+ status = mbox_append_message0 (tmpmailbox, mum->message,
+ &total, 1, (i == save_imapbase));
+ if (status != 0)
+ {
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ /* Clear the dirty bits. */
+ mum->attr_flags &= ~MU_ATTRIBUTE_MODIFIED;
+ message_clear_modified (mum->message);
+ }
+ else
+ {
+ /* Nothing changed copy the message straight. */
+ char buffer [1024];
+ size_t n;
+ off_t offset = mum->header_from;
+ size_t len = mum->body_end - mum->header_from;
+ while (len > 0)
+ {
+ n = (len < sizeof (buffer)) ? len : sizeof (buffer);
+ if ((status = stream_read (mailbox->stream, buffer, n, offset,
+ &n) != 0)
+ || (status = stream_write (tmpmailbox->stream, buffer, n,
+ total, &n) != 0))
+ {
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ len -= n;
+ total += n;
+ offset += n;
+ }
+ /* Add the newline separator. */
+ status = stream_write (tmpmailbox->stream, "\n", 1, total, &n);
+ if (status != 0)
+ {
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ total++;
+ }
+ } /* for (;;) */
+
+ /* Caution: before ftruncate()ing the file see
+ - if we've receive new mails. Some programs may not respect the lock,
+ - or the lock was held for too long.
+ - The mailbox may not have been properly updated before expunging. */
+ {
+ off_t size = 0;
+ if (stream_size (mailbox->stream, &size) == 0)
+ {
+ off_t len = size - mud->size;
+ off_t offset = mud->size;
+ char buffer [1024];
+ size_t n = 0;
+ if (len > 0 )
+ {
+ while ((status = stream_read (mailbox->stream, buffer,
+ sizeof (buffer), offset, &n)) == 0
+ && n > 0)
+ {
+ status = stream_write (tmpmailbox->stream, buffer, n,
+ total, &n);
+ if (status != 0)
+ {
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ total += n;
+ offset += n;
+ }
+ }
+ else if (len < 0)
+ {
+ /* Corrupted mailbox. */
+ mu_error ("Error expunge:%d: %s", __LINE__,
+ mu_strerror (status));
+ goto bailout0;
+ }
+ }
+ } /* End of precaution. */
+
+ /* Seek and rewrite it. */
+ if (total > 0)
+ {
+ char buffer [1024];
+ size_t n = 0;
+ off_t off = 0;
+ off_t offset = marker;
+ while ((status = stream_read (tmpmailbox->stream, buffer,
+ sizeof (buffer), off, &n)) == 0
+ && n > 0)
+ {
+ status = stream_write (mailbox->stream, buffer, n, offset, &n);
+ if (status != 0)
+ {
+ mu_error ("Error expunge:%d: %s\n", __LINE__,
+ mu_strerror (status));
+ goto bailout;
+ }
+ off += n;
+ offset += n;
+ }
+ }
+
+ /* Flush/truncation. Need to flush before truncate. */
+ stream_flush (mailbox->stream);
+ status = stream_truncate (mailbox->stream, total + marker);
+ if (status != 0)
+ {
+ mu_error ("Error expunging:%d: %s\n", __LINE__,
+ mu_strerror (status));
+ goto bailout;
+ }
+
+ /* Don't remove the tmp mbox in case of errors, when writing back. */
+ bailout0:
+ remove (tmpmboxname);
+
+ bailout:
+
+ free (tmpmboxname);
+ /* Release the File lock. */
+ locker_unlock (mailbox->locker);
+ mailbox_close (tmpmailbox);
+ mailbox_destroy (&tmpmailbox);
+
+ /* Reenable signal. */
+#ifdef WITH_PTHREAD
+ pthread_setcancelstate (state, &state);
+#endif
+ sigprocmask (SIG_UNBLOCK, &signalset, 0);
+
+ /* We need to readjust the pointers.
+ It is a little hairy because we need to keep the message pointers alive
+ So we are going through the array and "defragmentize". For example
+ in (1 2 3 4) if 2 was deleted we need to move 3 4 up by one etc ..
+ */
+ if (status == 0)
+ {
+ size_t dlast;
+ monitor_wrlock (mailbox->monitor);
+ for (j = dirty, dlast = mud->messages_count - 1;
+ j <= dlast; j++)
+ {
+ /* Clear all the references, any attach messages been already
+ destroy above. */
+ mum = mud->umessages[j];
+ if (remove_deleted && ATTRIBUTE_IS_DELETED (mum->attr_flags))
+ {
+ if ((j + 1) <= dlast)
+ {
+ /* Move all the pointers up. So the message pointer
+ part of mum will be at the right position. */
+ memmove (mud->umessages + j, mud->umessages + j + 1,
+ (dlast - j) * sizeof (mum));
+#if 0
+ mum->header_from = mum->header_from_end = 0;
+ mum->body = mum->body_end = 0;
+ mum->header_lines = mum->body_lines = 0;
+#endif
+ for (i = 0; i < HDRSIZE; i++)
+ if (mum->fhdr[i])
+ {
+ free (mum->fhdr[i]);
+ mum->fhdr[i] = NULL;
+ }
+ memset (mum, 0, sizeof (*mum));
+ /* We are not free()ing the useless mum, but instead
+ we put it back in the pool, to be reuse. */
+ mud->umessages[dlast] = mum;
+ dlast--;
+ /* Set mum to the new value after the memmove so it
+ gets cleared to. */
+ mum = mud->umessages[j];
+ }
+ else
+ {
+ for (i = 0; i < HDRSIZE; i++)
+ if (mum->fhdr[i])
+ {
+ free (mum->fhdr[i]);
+ mum->fhdr[i] = NULL;
+ }
+ memset (mum, 0, sizeof (*mum));
+ }
+ }
+ mum->header_from = mum->header_from_end = 0;
+ mum->body = mum->body_end = 0;
+ mum->header_lines = mum->body_lines = 0;
+ for (i = 0; i < HDRSIZE; i++)
+ if (mum->fhdr[i])
+ {
+ free (mum->fhdr[i]);
+ mum->fhdr[i] = NULL;
+ }
+ }
+ monitor_unlock (mailbox->monitor);
+ /* This is should reset the messages_count, the last argument 0 means
+ not to send event notification. */
+ mbox_scan0 (mailbox, dirty, NULL, 0);
+ }
+ return status;
+}
+
+static int
+mbox_expunge (mailbox_t mailbox)
+{
+ return mbox_expunge0 (mailbox, 1);
+}
+
+static int
+mbox_save_attributes (mailbox_t mailbox)
+{
+ return mbox_expunge0 (mailbox, 0);
+}
+
+static int
+mbox_message_uid (message_t msg, size_t *puid)
+{
+ mbox_message_t mum = message_get_owner (msg);
+ if (puid)
+ *puid = mum->uid;
+ return 0;
+}
+
+static int
+mbox_get_body_fd (stream_t is, int *pfd)
+{
+ body_t body = stream_get_owner (is);
+ message_t msg = body_get_owner (body);
+ mbox_message_t mum = message_get_owner (msg);
+ return mbox_get_fd (mum, pfd);
+}
+
+static int
+mbox_get_fd (mbox_message_t mum, int *pfd)
+{
+ int status;
+ if (mum == NULL)
+ return EINVAL;
+ status = stream_get_fd (mum->mud->mailbox->stream, pfd);
+ return status;
+}
+
+static int
+mbox_get_attr_flags (attribute_t attr, int *pflags)
+{
+ message_t msg = attribute_get_owner (attr);
+ mbox_message_t mum = message_get_owner (msg);
+
+ if (mum == NULL)
+ return EINVAL;
+ if (pflags)
+ *pflags = mum->attr_flags;
+ return 0;
+}
+
+static int
+mbox_set_attr_flags (attribute_t attr, int flags)
+{
+ message_t msg = attribute_get_owner (attr);
+ mbox_message_t mum = message_get_owner (msg);
+
+ if (mum == NULL)
+ return EINVAL;
+ mum->attr_flags |= flags;
+ return 0;
+}
+
+static int
+mbox_unset_attr_flags (attribute_t attr, int flags)
+{
+ message_t msg = attribute_get_owner (attr);
+ mbox_message_t mum = message_get_owner (msg);
+
+ if (mum == NULL)
+ return EINVAL;
+ mum->attr_flags &= ~flags;
+ return 0;
+}
+
+static int
+mbox_body_readline (stream_t is, char *buffer, size_t buflen,
+ off_t off, size_t *pnread)
+{
+ body_t body = stream_get_owner (is);
+ message_t msg = body_get_owner (body);
+ mbox_message_t mum = message_get_owner (msg);
+
+ return mbox_readstream (mum, buffer, buflen, off, pnread, 1,
+ mum->body, mum->body_end);
+}
+
+static int
+mbox_body_read (stream_t is, char *buffer, size_t buflen,
+ off_t off, size_t *pnread)
+{
+ body_t body = stream_get_owner (is);
+ message_t msg = body_get_owner (body);
+ mbox_message_t mum = message_get_owner (msg);
+ return mbox_readstream (mum, buffer, buflen, off, pnread, 0,
+ mum->body, mum->body_end);
+}
+
+static int
+mbox_readstream (mbox_message_t mum, char *buffer, size_t buflen,
+ off_t off, size_t *pnread, int isreadline,
+ off_t start, off_t end)
+{
+ size_t nread = 0;
+ int status = 0;
+
+ if (buffer == NULL || buflen == 0)
+ {
+ if (pnread)
+ *pnread = nread;
+ return 0;
+ }
+
+ monitor_rdlock (mum->mud->mailbox->monitor);
+#ifdef WITH_PTHREAD
+ /* read() is cancellation point since we're doing a potentially
+ long operation. Lets make sure we clean the state. */
+ pthread_cleanup_push (mbox_cleanup, (void *)mum->mud->mailbox);
+#endif
+ {
+ off_t ln = end - (start + off);
+ if (ln > 0)
+ {
+ /* Position the file pointer and the buffer. */
+ nread = ((size_t)ln < buflen) ? (size_t)ln : buflen;
+ if (isreadline)
+ status = stream_readline (mum->mud->mailbox->stream, buffer, buflen,
+ start + off, &nread);
+ else
+ status = stream_read (mum->mud->mailbox->stream, buffer, nread,
+ start + off, &nread);
+ }
+ }
+ monitor_unlock (mum->mud->mailbox->monitor);
+#ifdef WITH_PTHREAD
+ pthread_cleanup_pop (0);
+#endif
+
+ if (pnread)
+ *pnread = nread;
+ return status;
+}
+
+static int
+mbox_header_fill (header_t header, char *buffer, size_t len,
+ off_t off, size_t *pnread)
+{
+ message_t msg = header_get_owner (header);
+ mbox_message_t mum = message_get_owner (msg);
+ size_t j;
+ /* Since we are filling the header there is no need for the cache headers
+ discard them. */
+ for (j = 0; j < HDRSIZE; j++)
+ {
+ if (mum->fhdr[j])
+ {
+ free (mum->fhdr[j]);
+ mum->fhdr[j] = NULL;
+ }
+ }
+ return mbox_readstream (mum, buffer, len, off, pnread, 0,
+ mum->header_from_end, mum->body);
+}
+
+static int
+mbox_header_get_fvalue (header_t header, const char *name, char *buffer,
+ size_t buflen, size_t *pnread)
+{
+ size_t i, fv_len = 0;
+ message_t msg = header_get_owner (header);
+ mbox_message_t mum = message_get_owner (msg);
+ int err = ENOENT;
+ for (i = 0; i < HDRSIZE; i++)
+ {
+ if (*name == *(fhdr_table[i]) && strcasecmp (fhdr_table[i], name) == 0)
+ {
+ if (mum->fhdr[i])
+ {
+ fv_len = strlen (mum->fhdr[i]);
+ if (buffer && buflen > 0)
+ {
+ /* For the null. */
+ buflen--;
+ fv_len = (fv_len < buflen) ? fv_len : buflen;
+ memcpy (buffer, mum->fhdr[i], fv_len);
+ buffer[fv_len] = '\0';
+ }
+ err = 0;
+ }
+ else
+ err = ENOENT;
+ break;
+ }
+ }
+
+ if (pnread)
+ *pnread = fv_len;
+ return err;
+}
+
+static int
+mbox_header_size (header_t header, size_t *psize)
+{
+ message_t msg = header_get_owner (header);
+ mbox_message_t mum = message_get_owner (msg);
+ if (mum == NULL)
+ return EINVAL;
+ if (psize)
+ *psize = mum->body - mum->header_from_end;
+ return 0;
+}
+
+static int
+mbox_header_lines (header_t header, size_t *plines)
+{
+ message_t msg = header_get_owner (header);
+ mbox_message_t mum = message_get_owner (msg);
+ if (mum == NULL)
+ return EINVAL;
+ if (plines)
+ *plines = mum->header_lines;
+ return 0;
+}
+
+static int
+mbox_body_size (body_t body, size_t *psize)
+{
+ message_t msg = body_get_owner (body);
+ mbox_message_t mum = message_get_owner (msg);
+ if (mum == NULL)
+ return EINVAL;
+ if (psize)
+ *psize = mum->body_end - mum->body;
+ return 0;
+}
+
+static int
+mbox_stream_size (stream_t stream, off_t *psize)
+{
+ body_t body = stream_get_owner (stream);
+ return mbox_body_size (body, (size_t*) psize);
+}
+
+static int
+mbox_body_lines (body_t body, size_t *plines)
+{
+ message_t msg = body_get_owner (body);
+ mbox_message_t mum = message_get_owner (msg);
+ if (mum == NULL)
+ return EINVAL;
+ if (plines)
+ *plines = mum->body_lines;
+ return 0;
+}
+
+static int
+mbox_envelope_date (envelope_t envelope, char *buf, size_t len,
+ size_t *pnwrite)
+{
+ message_t msg = envelope_get_owner (envelope);
+ mbox_message_t mum = message_get_owner (msg);
+ size_t n = 0;
+ int status;
+ char buffer[512];
+ char *s;
+
+ if (mum == NULL)
+ return EINVAL;
+
+ status = stream_readline (mum->mud->mailbox->stream, buffer, sizeof(buffer),
+ mum->header_from, &n);
+ if (status != 0)
+ {
+ if (pnwrite)
+ *pnwrite = 0;
+ return status;
+ }
+
+ /* Format: "From [sender] [date]" */
+ /* strlen ("From ") == 5 */
+ if (n > 5 && (s = strchr (buffer + 5, ' ')) != NULL)
+ {
+ if (buf && len > 0)
+ {
+ len--; /* Leave space for the null. */
+ strncpy (buf, s + 1, len)[len] = '\0';
+ len = strlen (buf);
+ }
+ else
+ len = strlen (s + 1);
+ }
+ else
+ len = 0;
+
+ if (pnwrite)
+ *pnwrite = len;
+ return 0;
+}
+
+static int
+mbox_envelope_sender (envelope_t envelope, char *buf, size_t len,
+ size_t *pnwrite)
+{
+ message_t msg = envelope_get_owner (envelope);
+ mbox_message_t mum = message_get_owner (msg);
+ size_t n = 0;
+ int status;
+ char buffer[512];
+ char *s;
+
+ if (mum == NULL)
+ return EINVAL;
+
+ status = stream_readline (mum->mud->mailbox->stream, buffer, sizeof(buffer),
+ mum->header_from, &n);
+ if (status != 0)
+ {
+ if (pnwrite)
+ *pnwrite = 0;
+ return status;
+ }
+
+ /* Format: "From [sender] [date]" */
+ /* strlen ("From ") == 5 */
+ if (n > 5 && (s = strchr (buffer + 5, ' ')) != NULL)
+ {
+ /* Put a NULL to isolate the sender string, make a C string. */
+ *s = '\0';
+ if (buf && len > 0)
+ {
+ l