Skip to content

Instantly share code, notes, and snippets.

@stas
Created May 22, 2010 23:30
Show Gist options
  • Save stas/410459 to your computer and use it in GitHub Desktop.
Save stas/410459 to your computer and use it in GitHub Desktop.
--- alpine-1.0+dfsg.orig/imap/src/c-client/mail.h
+++ alpine-1.0+dfsg/imap/src/c-client/mail.h
@@ -857,6 +857,7 @@
unsigned int spare7 : 1; /* seventh spare bit */
unsigned int spare8 : 1; /* eighth spare bit */
void *sparep; /* spare pointer */
+ char *maildirp; /* for the Maildir driver */
unsigned long user_flags; /* user-assignable flags */
} MESSAGECACHE;
--- alpine-1.0+dfsg.orig/imap/src/osdep/unix/dummy.c
+++ alpine-1.0+dfsg/imap/src/osdep/unix/dummy.c
@@ -433,7 +433,10 @@
}
d = NIL; /* don't \NoSelect dir if it has a driver */
if ((attributes & LATT_NOSELECT) && (d = mail_valid (NIL,name,NIL)) &&
- (d != &dummydriver)) attributes &= ~LATT_NOSELECT;
+ (d != &dummydriver)) {
+ attributes &= ~LATT_NOSELECT;
+ attributes |= LATT_NOINFERIORS;
+ }
if (!contents || /* notify main program */
(!(attributes & LATT_NOSELECT) && (csiz = strlen (contents)) &&
(s = mailboxfile (tmp,name)) &&
@@ -441,7 +444,7 @@
!stat (s,&sbuf) && (d || (csiz <= sbuf.st_size)) &&
SAFE_SCAN_CONTENTS (d,tmp,contents,csiz,sbuf.st_size)))
mm_list (stream,delimiter,name,attributes);
- return T;
+ return (attributes & LATT_NOINFERIORS) ? NIL : T;
}
/* Dummy create mailbox
--- /dev/null
+++ alpine-1.0+dfsg/imap/src/osdep/unix/maildir.c
@@ -0,0 +1,2105 @@
+/* maildir.c
+ * c-client Maildir driver
+ * (** intended only for use with PINE, not for UW-IMAP!! **)
+ *
+ * Copyright (C) 2004 Glue Logic LLC All rights reserved code()gluelogic.com
+ * Portions copyright University of Washington.
+ * License: essentially the _modified_ BSD license or X11 license
+ * (See http://www.gnu.org/licenses/license-list.html)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * 2004.02.04 gps-0.01 Glenn Strauss code()gluelogic.com
+ * ==Big thanks to Brian Fisk and danah boyd for alpha testing
+ * - Major overhaul of maildir patch; largely rewritten
+ * - Added support to save message to maildir (old code crashed and burned)
+ * - Added bounds checking (old code had precious little)
+ * - Ripped out ass-backwards UID code.
+ * Temporarily use device inode number for UID (which violates the strict
+ * ascending IMAP UID requirement, and inodes can be reused, i.e. they might
+ * not be unique across sessions if a file is deleted and the inode reused)
+ * NOTE: this means that this code should not be used to add Maildir support
+ * to UW-IMAPd. This patch is only intended for adding Maildir support to
+ * PINE. This sysadmin prefers other IMAP servers to UW-IMAPd, primarily
+ * because some others support Maildir natively and prioritize security over
+ * compatibility.
+ * - Much better memory resource usage
+ * - Concurrency checks and updates for shared mailboxes
+ * - Much better unique filenames and compliance with Maildir protocol
+ * Also, makes effort to transparently preserve unrecognized flags
+ * (and is able to do so with flag updates and copies between maildirs,
+ * but is unable to do so for maildir appends since that info is not avail)
+ * - Workaround for Pine bug not checking dtb->status before using it
+ * (Pine should check that mailbox driver defines status routine,
+ * or should fall back to mail_status_default() automatically)
+ * 2004.10.04 gps-0.02 Glenn Strauss code()gluelogic.com
+ * - Fix NULL pointer dereference (crashing bug) in seldom used codepath
+ * (reported by Brian Fisk)
+ * - Updated patch offsets and such to work with PINE 4.61
+ * 2004.11.15 gps-0.03 Glenn Strauss code()gluelogic.com
+ * - Workaround mail_fetch_structure() not checking return value from
+ * maildir_fetchtext(), resulting in a crash when a message is removed out
+ * from under PINE while in folder view, and then user goes to read message.
+ * (reported by Eduardo Chappa)
+ * 2004.11.20 gps-0.04 Glenn Strauss code()gluelogic.com
+ * - Workaround mail_fetch_structure() not checking return value from
+ * maildir_fetchtext() (2nd try) by rescanning message cache for message.
+ * - Rescan message cache and recover if message body fetch or flag update
+ * fails, and the cause was that message flags were externally modified.
+ * - Silence warning when only flags modified in externally modified folder
+ * - Silence all "expunged" messages when reloading externally modified folder
+ * (Directly hack into internal PINE client (PER_STREAM_S *)->expunge_count)
+ * - Created minimal maildir_list() to support inbox-path = #md/inbox
+ * (all reported by Eduardo Chappa)
+ *
+ * Notes:
+ *
+ * - YMMV: my compile line for Pine 4.61 under RedHat Fedora Core 1,
+ * with Pine defaulting to Maildir when creating mailboxes:
+ * ./build lrh DEBUG="-g -O2" NOLDAP \
+ * EXTRASPECIALS="CREATEPROTO=maildirproto EMPTYPROTO=maildirproto"
+ * - I do not know why the original maildir patch modified
+ * pine4.61/imap/src/osdep/unix/dummy.c
+ * pine4.61/pine/mailcmd.c
+ * Those modifications are copied from the original maildir patch
+ * - Maildir protocol specifies the use of link()/unlink() when creating files
+ * in Maildir subdirectories (tmp/ new/ cur/). This implementation uses
+ * link()/unlink() when copying files between Maildirs. It uses rename()
+ * when updating Maildir flags in the message filename and when moving files
+ * between new/ and cur/. Files in new/ and cur/ should be unique according
+ * to the Maildir spec, and therefore, if the target file exists, it must have
+ * been the result of a failed rename() or link()/unlink() interrupted
+ * mid-stream that left files in both places. Therefore, the target should be
+ * removed and replaced. rename() will replace an existing file, whereas
+ * link() will fail, which is not something that a remote user would be able
+ * to fix manually (e.g. client to imapd). (N.B. rename() is not guaranteed
+ * atomic on all platforms and the POSIX spec makes no such requirement.
+ * And the link()/unlink() combination is, of course, not atomic.)
+ * - While this driver supports the Courier Maildir+ extension ",S=..." in
+ * filenames, and even adds it where missing, this driver does not verify that
+ * the size is correct. This driver does not use the size information from
+ * the filename other than to provide a hint about the size to the display.
+ * The actual size is determined when the message is read, which is necessary
+ * for displaying the From, To, and Subject in the summary listing.
+ * - When appending messages to a mailbox, PINE does not provide an interface to
+ * the original envelope, so the mtime set on the Maildir message file (used
+ * for message arrival time -- time message was delivered locally) is lost
+ * and the date in the message headers is used. This limitation is present
+ * in all PINE mailbox drivers.
+ * - When stream->silent is set, the higher levels of the program are not
+ * notified during some callbacks to mid-level APIs and higher level state
+ * counters will not be updated. Therefore, this driver makes an attempt not
+ * to make any such callbacks when stream->silent is set. This may result in
+ * extra (unnecessary) directory scans because the last scantime is not
+ * updated, even though it may have been modified by a known action. (It is
+ * not modified because other actions should occur whenever last scantime is
+ * updated and these actions are skipped to avoid callbacks.)
+ *
+ * Future possibile optimizations and enhancements:
+ *
+ * - Add pattern support to maildir_list() and maildir_lsub()
+ * (For examples, see mh.c driver)
+ * - Consider adding support for custom flags (elt->user_flags?)
+ * - Consider implementing maildirquota (Maildir+ from Courier) in
+ * maildir_append_msg() and maildir_move_new()
+ * - Consider using mmap() on files, when available
+ * - Consider blocking Ctrl-C (and/or other signals) in certain places
+ * (using MM_CRITICAL() and MM_NOCRITICAL() might take care of this for us)
+ * - Look to see if it is possible to avoid the wasteful CRLF translations,
+ * maybe by modifying maildir_fetch_msg_core() to directly fill out other
+ * elt->private.msg structures. Look into driver DR_CRLF flag.
+ * - UID support to enable use of Maildir with UW-IMAPd
+ * (and then remove DR_NOSTICKY driver flag)
+ * maybe store message filename => UID mapping in a read-only file that is
+ * appended to, and locked and cleaned out occasionally.
+ * (a la Dovecot's <Maildir>/dovecot-uidlist)
+ * (maybe change cur/ directory to temporarily add sticky bit as a lock, and
+ * to remove it when done, or others will remove it in an hour. The cur/
+ * directory is stat'd before scans of the directory, so such a lock could
+ * be checked without additional syscall overhead and without file locking)
+ * Whatever mechanism is chosen, the PINE client should not suffer overhead.
+ * (Check LOCAL->svc; see maildir_open() for how LOCAL-svc is filled)
+ * - Make maildir_path() stricter in the characters/encodings allowed in the
+ * final path segment (maildir folder name)
+ *
+ *
+ * HISTORY (ancient)
+ * -------
+ *
+ * Maildir Module for PINE 4.0x - fourth release, use with CARE!
+ * Author: Mattias Larsson <[email protected]>
+ * Version: 21.07.98
+ *
+ * Please read the README.maildir file before using this module!
+ * If you have any questions, please e-mail [email protected]
+ * Multiple inboxes patch by Dean Gaudet <[email protected]>
+ *
+ * =================================================
+ *
+ * Based on the IMAP2 maildir routines by:
+ *
+ * Author: Eric Green
+ * Bloodhounds International Inc.
+ * [email protected]
+ *
+ * Additional contributions from:
+ * Aidas Kasparas ([email protected])
+ *
+ * Date: 27 April 1997
+ * Last Edited: 13 June 1997
+ *
+ * Based (heavily) on mh.c and other c-client library files by Mark Crispin:
+ *
+ * Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: [email protected]
+ *
+ * Copyright 1995 by the University of Washington
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appears in all copies and that both the
+ * above copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the University of Washington not be
+ * used in advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. This software is made
+ * available "as is", and
+ * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
+ * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
+ * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
+ * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+/*
+ * CONFIGURABLE OPTIONS - PLEASE CHECK THESE OUT
+ */
+
+#define NO_MAILDIR_FIDDLE /* disallow Maildir with "Maildir" string in the
+ name. This is useful in an ISP setup using the
+ IMAP daemon. #undef it if you are running a
+ normal pine and know what you are doing */
+#undef NO_MAILDIR_FIDDLE
+
+#define NO_ABSOLUTE_PATHS /* if you define this, leading '/'s will be
+ removed from mailbox paths and mailbox paths
+ will not be allowed to contain "../"
+ backreferences. This is also useful in an ISP
+ setup with IMAP */
+#undef NO_ABSOLUTE_PATHS
+
+#define DIR_MASK 0770 /* mkdir() mode mask. 0700 is default, but in
+ group environments with group access to
+ mailboxes, 0770 is more convenient. The system
+ must be set up properly, e.g. directories should
+ be created under group-sticky directories that
+ inherit parent group ownership. */
+
+#define FILE_MASK (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+ /* open() mode mask. Similar to mkdir() mask,
+ but for saving message files
+ default: (S_IRUSR|S_IWUSR)
+ group example: (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+ */
+
+#define MAILDIR_MAX_FLAGS 32 /* max number of flags to allow on Maildir
+ Must be >= 5 */
+
+
+#define restrict /* remove this if your compiler supports the
+ 'restrict' keyword; e.g. gcc -std=c99 */
+
+#define PINE_HACK_EXPUNGE /* The c-client library terribly overloads the
+ stream->silent flag. Define this to cause the
+ maildir driver to modify PINE client private
+ structure elements to quell spurious "expunged"
+ messages when an external program modifies an
+ open maildir. A single "externally modified"
+ message will still be given. */
+
+
+/*
+ * END CONFIGURATION
+ */
+
+
+#include "mail.h"
+#include "osdep.h"
+#include "misc.h"
+#include "dummy.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <utime.h>
+
+/* include pine/pine.h for (PER_STREAM_S *) */
+/* define the minimum necessary so that pine/pine.h #include's cleanly */
+/* (c-client library (badness) does not protect itself from multiple includes)*/
+#ifdef PINE_HACK_EXPUNGE
+ #define SigType void
+ #define PROTO(args) args
+ #define HelpType const char * const *
+ #define MAXFOLDER 128
+ #define MAX_SCREEN_COLS 170
+ #if defined(PATH_MAX)
+ #define MAXPATH PATH_MAX
+ #elif defined(MAXPATHLEN)
+ #define MAXPATH MAXPATHLEN
+ #else
+ #define MAXPATH 256
+ #endif
+ #include <setjmp.h>
+ #include "../../pico/estruct.h"
+ #include "../../pico/pico.h"
+ #include "../../alpine/alpine.h"
+#endif /* PINE_HACK_EXPUNGE */
+
+#include "maildir.h"
+
+/* Driver dispatch used by MAIL */
+
+DRIVER maildirdriver = {
+ "maildir", /* driver name */
+ /* driver flags */
+ DR_MAIL|DR_LOCAL|DR_NOFAST|DR_NAMESPACE|DR_RECYCLE|DR_NOSTICKY,
+ (DRIVER *) NIL, /* next driver */
+ maildir_valid, /* mailbox is valid for us */
+ maildir_parameters, /* manipulate parameters */
+ NIL, /* scan mailboxes */
+ maildir_list, /* find mailboxes */
+ maildir_lsub, /* find subscribed mailboxes */
+ NIL, /* maildir_sub, */ /* subscribe to mailbox */
+ NIL, /* maildir_unsub, */ /* unsubscribe from mailbox */
+ maildir_create, /* create mailbox */
+ maildir_delete, /* delete mailbox */
+ maildir_rename, /* rename mailbox */
+ mail_status_default, /* status of mailbox */
+ maildir_open, /* open mailbox */
+ maildir_close, /* close mailbox */
+ NIL, /* maildir_fetchfast, *//* fetch message "fast" attributes */
+ NIL, /* fetch message flags */
+ NIL, /* fetch overview */
+ NIL, /* fetch message envelopes */
+ maildir_fetchheader, /* fetch message header */
+ maildir_fetchtext, /* fetch message body */
+ NIL, /* fetch partial message text */
+ NIL, /* unique identifier */
+ NIL, /* message number */
+ NIL, /* modify flags */
+ maildir_flagmsg, /* per-message modify flags */
+ NIL, /* search for message based on criteria */
+ NIL, /* sort messages */
+ NIL, /* thread messages */
+ maildir_ping, /* ping mailbox to see if still alive */
+ maildir_check, /* check for new messages */
+ maildir_expunge, /* expunge deleted messages */
+ maildir_copy, /* copy messages to another mailbox */
+ maildir_append, /* append string message to mailbox */
+ NIL /* garbage collect stream */
+};
+
+ /* prototype stream */
+MAILSTREAM maildirproto = {&maildirdriver};
+
+
+static __inline__ int
+has_md_prefix(const char * const restrict mailbox)
+{
+ return ( mailbox[0] == '#'
+ && (mailbox[1] == 'M' || mailbox[1] == 'm')
+ && (mailbox[2] == 'D' || mailbox[2] == 'd')
+ && mailbox[3] == '/');
+}
+
+
+static __inline__ int
+is_md_inbox(const char * restrict mailbox)
+{
+ if (has_md_prefix(mailbox))
+ mailbox += 4;
+ return ( (mailbox[0] == 'I' || mailbox[0] == 'i')
+ && (mailbox[1] == 'N' || mailbox[1] == 'n')
+ && (mailbox[2] == 'B' || mailbox[2] == 'b')
+ && (mailbox[3] == 'O' || mailbox[3] == 'o')
+ && (mailbox[4] == 'X' || mailbox[4] == 'x')
+ && mailbox[5] == '\0');
+}
+
+
+/* generate path string to cur/ subdirectory of given Maildir
+ * (caller should (implicitly or explicitly) check that *dst != '\0' on return)
+ */
+static char *maildir_path (char * const restrict dst, const size_t dst_size,
+ const char * const restrict name)
+{
+ const char *maildir = name;
+ const char *s = NULL;
+
+ /* (skip over internal prefix (case-insensitive) "#md/...", if present) */
+ if (has_md_prefix(name))
+ maildir += 4;
+
+ #ifdef NO_ABSOLUTE_PATHS
+ while (*maildir == '/')
+ maildir++;
+ if (!(maildir[0] == '.' && maildir[1] == '.'
+ && (maildir[2] == '/' || maildir[2] == '\0')) && maildir[0] != '\0') {
+ s = name;
+ while ((s = strstr(s, "/..")) && s[3] != '/' && s[3] != '\0')
+ ;
+ }
+ else
+ s = "";
+ #endif
+
+ if (*maildir != '/') {
+ if (is_md_inbox(name))
+ maildir = MAILDIRPATH;
+ if (snprintf (dst,dst_size,"%s/%s/cur",myhomedir(),maildir) >= dst_size)
+ s = "";
+ }
+ else { /* allow absolute paths */
+ if (snprintf (dst, dst_size, "%s/cur", maildir) >= dst_size)
+ s = "";
+ }
+
+ if (s != NULL) {
+ snprintf (dst, dst_size, "Path to maildir (%s) too long or invalid",
+ maildir);
+ mm_log (dst,WARN);
+ *dst = '\0';
+ }
+
+ return dst;
+}
+
+
+static int maildir_isvalid (const char * const restrict name,
+ const long justname)
+{
+ if (name == NULL
+ || name[0] == '\0' /* disallow "", ".", and ".." */
+ || (name[0]=='.' && (name[1]=='\0' || (name[1]=='.' && name[2]=='\0')))
+ || (name[0] == '#' && !has_md_prefix(name)))
+ return NIL;
+
+ #ifdef NO_MAILDIR_FIDDLE
+ /* ignore anything containing Maildir to prevent anyone from fiddling
+ with their incoming Maildir directly; it should be accessed via the
+ INBOX alias */
+ if (strstr(name, "Maildir")) {
+ return NIL;
+ }
+ #endif
+
+ if (justname && *name == '#')
+ /* done; only checking validity of string */
+ return T;
+ else if (strlen(name) < MAILTMPLEN) {
+ /* assume valid local maildir if dir exists and contains cur/ dir */
+ char tmp[MAILTMPLEN];
+ struct stat sbuf;
+
+ /* (must be local pine path (no internal prefix of '*' or '{')) */
+ if (name[0] != '*' && name[0] != '{'
+ && maildir_path (tmp,sizeof(tmp),name)
+ && stat (tmp,&sbuf) == 0 && S_ISDIR (sbuf.st_mode))
+ return T;
+ }
+
+ return NIL;
+}
+
+
+static int
+ascii_sort (const void *a, const void *b)
+{
+ return (*((char *)a) - *((char *)b));
+}
+
+
+static void
+maildir_add_flags (char * const s, size_t size, MESSAGECACHE * const elt,
+ char * const addtl_flags)
+{
+ char flags[MAILDIR_MAX_FLAGS],aflags[MAILDIR_MAX_FLAGS];
+ size_t i, a = 0;
+
+ /* size must be > 0 or else string will not be NIL terminated upon return!
+ * size should be > MAILDIR_MAX_FLAGS, or else flags will be silently
+ * truncated
+ */
+ /* assert(size); */
+
+ /* extract unknown flags (flags after MAILDIR_MAX_FLAGS silently ignored) */
+ if (addtl_flags) {
+ for (i = 0; a < sizeof(aflags) && addtl_flags[i]; i++) {
+ switch (addtl_flags[i]) {
+ case 'S': case 'R': case 'F': case 'T': case 'D':
+ break;
+ default:
+ aflags[a++] = addtl_flags[i];
+ break;
+ }
+ }
+ }
+
+ /* write known flags */
+ i = 0;
+ if (elt->draft) flags[i++] = 'D';
+ if (elt->flagged) flags[i++] = 'F';
+ if (elt->answered) flags[i++] = 'R';
+ if (elt->seen) flags[i++] = 'S';
+ if (elt->deleted) flags[i++] = 'T';
+
+ /* append unknown flags
+ * perform an ASCII sort of the flags (required by Maildir specification)
+ * (eliminate all unknown flags if there are too many) */
+ if (a) {
+ if (i + a <= sizeof(flags)) {
+ memcpy(flags+i,aflags,a);
+ if ((i += a) > 1)
+ qsort(flags,i,sizeof(char),ascii_sort);
+ }
+ }
+
+ /* commit flags to s and NIL terminate string
+ * (flags are silently truncated if not enough space in s) */
+ if ((i = (size > i) ? i : size - 1))
+ memcpy(s, flags, i);
+ s[i] = '\0';
+}
+
+
+/* predeclare */
+static int
+maildir_refresh_elt (MAILSTREAM * const stream, MESSAGECACHE * const elt);
+
+
+static int
+maildir_flagmsg_rename (MAILSTREAM * const stream, MESSAGECACHE * const elt)
+{
+ /* In testing, I found that when marking/unmarking a message for deletion,
+ * this routine is called twice, first without the changed flag and then
+ * with the changed flag. Calling this routine twice is a little extra
+ * work in PINE, but we avoid making any syscalls the case where there is no
+ * modification, so this is not a terrible worry -- just a little wasteful.
+ */
+ char file_old[MAILTMPLEN],file_new[MAILTMPLEN];
+ char *s,*t;
+ const size_t i = elt->maildirp ? strlen(elt->maildirp) : 0;
+ const size_t dir_len = strlen(LOCAL->dir);
+
+ if (i == 0)
+ return;
+
+ /* (filenames are of the form "%s/%s:2,DFRST" for dir and file) */
+ /* (+4 for "/" and ":2,")*/
+ if (dir_len+i+4+MAILDIR_MAX_FLAGS >= sizeof(file_new))
+ return NIL;
+ memcpy(file_old, LOCAL->dir, dir_len);
+ file_old[dir_len] = '/';
+ memcpy(file_old+dir_len+1, elt->maildirp, i+1);
+ memcpy(file_new, file_old, dir_len+1+i+1);
+ if ((s = strrchr(file_new,':')) && (s[1]=='2' || s[1]=='3') && s[2]==',') {
+ /* compare against existing flags and return if no changes needed */
+ s[1] = '2';
+ t = (s += 3); /*(requires flags in ASCII order or will be reordered)*/
+ if ( (elt->draft ? (t = strchr(t,'D')) != NULL : !strchr(t,'D'))
+ && (elt->flagged ? (t = strchr(t,'F')) != NULL : !strchr(t,'F'))
+ && (elt->answered ? (t = strchr(t,'R')) != NULL : !strchr(t,'R'))
+ && (elt->seen ? (t = strchr(t,'S')) != NULL : !strchr(t,'S'))
+ && (elt->deleted ? (t = strchr(t,'T')) != NULL : !strchr(t,'T')))
+ return NIL;
+
+ }
+ else {
+ s = file_new+dir_len+1+i;
+ *s++ = ':'; *s++ = '2'; *s++ = ','; *s = '\0';
+ }
+
+ maildir_add_flags (s, MAILDIR_MAX_FLAGS+1, elt, s);
+
+ /* rename the file with new flags */
+ if (rename (file_old,file_new) < 0) {
+ if (errno == ENOENT && maildir_refresh_elt(stream, elt))
+ return maildir_flagmsg_rename(stream, elt);
+ else {
+ snprintf(file_old, sizeof(file_old),
+ "Unable to write flags to disk: %s", strerror (errno));
+ mm_log(file_old,ERROR);
+ return NIL;
+ }
+ }
+
+ /* update the file name in cache */
+ fs_give ((void **) &elt->maildirp);
+ elt->maildirp = cpystr (file_new+dir_len+1);
+ return T;
+}
+
+
+static void
+maildir_flagmsg_size (MAILSTREAM * const stream, MESSAGECACHE * const elt)
+{
+ /* silently skip adding size if any problems are encountered; not fatal */
+ char path_old[MAILTMPLEN],path_new[MAILTMPLEN];
+ const size_t dir_len = strlen(LOCAL->dir);
+ size_t file_len = strlen(elt->maildirp);
+ struct stat st;
+ /*(expected to succeed because of code in maildir_flagmsg_init())*/
+ char * const f = strrchr(elt->maildirp,':');
+
+ /* (+1 for "/", +13 for ",S=9999999999") */
+ if (f && dir_len + 1 + file_len + 13 < sizeof(path_new)) {
+ memcpy(path_old, LOCAL->dir, dir_len);
+ path_old[dir_len] = '/';
+ memcpy(path_old+dir_len+1, elt->maildirp, file_len+1);
+ file_len = (f - elt->maildirp);
+ memcpy(path_new, path_old, dir_len+1+file_len);
+ if (stat (path_old,&st) == 0
+ && st.st_size <= 4294967295U) { /*(space in string checked above)*/
+ snprintf(path_new+dir_len+1+file_len,
+ sizeof(path_new)-(dir_len+1+file_len),
+ ",S=%lu%s", (unsigned long) st.st_size, f);
+ if (rename (path_old,path_new) == 0) {
+ fs_give ((void **) &elt->maildirp);
+ elt->maildirp = cpystr(path_new+dir_len+1);
+ }
+ }
+ }
+}
+
+
+static void
+maildir_flagmsg_init (MAILSTREAM * const stream, MESSAGECACHE * const elt,
+ char * const restrict d_name)
+{
+ char *s;
+ if (elt->maildirp)
+ fs_give ((void **) &elt->maildirp);
+ elt->maildirp = cpystr (d_name);
+ if ((s = strrchr(elt->maildirp,':'))
+ && (s[1] == '2' || s[1] == '3') && s[2] == ',') {
+ /* grab flags (":2,..." or old maildir patch ":3,..." at string end) */
+ if (s[1] == '3') { /*(should be rare; convert old PINE maildir patch)*/
+ s[1] = '2';
+ rename(d_name, elt->maildirp);
+ }
+ s += 3; /*(parse flags even if out of ASCII order)*/
+ elt->draft = (strchr (s,'D') != NULL);
+ elt->flagged = (strchr (s,'F') != NULL);
+ elt->answered = (strchr (s,'R') != NULL);
+ elt->seen = (strchr (s,'S') != NULL);
+ elt->deleted = (strchr (s,'T') != NULL);
+ /* Courier-IMAP uses "DFRST", too. */
+ /* Dovecot additionally allows 'a'-'z' custom flags */
+ }
+ else
+ /* (rename file with appended ":2,..." flag marker and flags) */
+ maildir_flagmsg_rename (stream, elt);
+
+ /* add file size to filename, if not present
+ * code kept separate from above for simplicity,
+ * even though it may result in extra rename() syscalls */
+ if (!strstr(elt->maildirp, ",S="))
+ maildir_flagmsg_size(stream, elt);
+}
+
+
+static __inline__ int
+maildir_select (const struct dirent * const restrict name)
+{
+ /* message filenames begin with timestamp in seconds */
+ switch (name->d_name[0]) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return T;
+ default : return NIL;
+ }
+}
+
+
+/* PINE provides a terrible prototype for this in the "name of compatibility"?!
+ * It uses its own scandir() implemented using qsort() rather than the OS's, if
+ * present. scandir() should be prototyped (but is not prototyped by PINE) as:
+ * int scandir(const char *dir, struct dirent ***namelist,
+ * int(*filter)(const struct dirent *),
+ * int(*compar)(const struct dirent **, const struct dirent **));
+ */
+static int
+maildir_namesort (const void * const v1, const void * const v2)
+{
+ const struct dirent * const * const restrict d1 = v1;
+ const struct dirent * const * const restrict d2 = v2;
+ /* message filenames begin with timestamp in seconds.
+ * sort on that, but if delivered in the same second,
+ * sort on filename string (not precise, depending on file name conventions)
+ */
+ const unsigned long t1 = strtoul((*d1)->d_name, NULL, 10);
+ const unsigned long t2 = strtoul((*d2)->d_name, NULL, 10);
+ return (t1 > t2 ? 1 : t1 < t2 ? -1 : strcmp ((*d1)->d_name,(*d2)->d_name));
+}
+
+
+/* remove files from tmp/ that are older than 36 hours */
+static void
+maildir_remove_tmp_old (MAILSTREAM * const stream)
+{
+ char file_tmp[MAILTMPLEN];
+ const int dir_len =
+ snprintf(file_tmp, sizeof(file_tmp), "%s/../tmp", LOCAL->dir);
+ size_t file_len;
+ struct dirent *d;
+ struct stat sbuf;
+ DIR *dir;
+ const time_t cutoff = time(NULL) - 129600; /* (129600 secs == 36 hours) */
+
+ if (dir_len >= sizeof(file_tmp)
+ || stat (file_tmp,&sbuf) < 0 || !S_ISDIR(sbuf.st_mode)
+ || !(dir = opendir (file_tmp))) {
+ snprintf (file_tmp, sizeof(file_tmp), "Unable to open maildir: %s",
+ strerror (errno));
+ mm_log (file_tmp,ERROR);
+ return;
+ }
+
+ file_tmp[dir_len] = '/';
+
+ while ((d = readdir (dir))) {
+ /* select only maildir message filename format, regular file,
+ skip filenames too long (+1 for "/" between dir and filenames),
+ unlink files older than 36 hours */
+ if (maildir_select(d)
+ && dir_len + (file_len = strlen(d->d_name)) + 1 < sizeof(file_tmp)
+ && (memcpy (file_tmp+dir_len,d->d_name,file_len+1),
+ stat(file_tmp, &sbuf) == 0)
+ && S_ISREG (sbuf.st_mode)
+ && sbuf.st_mtime < cutoff)
+ unlink (file_tmp);
+ }
+
+ closedir (dir);
+}
+
+
+/* move new messages from new/ to cur/ */
+static long
+maildir_move_new (MAILSTREAM * const stream)
+{
+ char file_new[MAILTMPLEN],file_cur[MAILTMPLEN],*f;
+ const int dir_len =
+ snprintf(file_new, sizeof(file_new), "%s/../new", LOCAL->dir);
+ size_t file_len;
+ struct dirent *d;
+ struct stat sbuf;
+ DIR *dir;
+ long recent = 0;
+
+ /* return if new/ has not changed since last scan, else process new/ dir */
+ if (dir_len >= sizeof(file_new)
+ || stat (file_new,&sbuf) < 0 || !S_ISDIR(sbuf.st_mode)) {
+ snprintf (file_new, sizeof(file_new), "Unable to open maildir: %s",
+ strerror (errno));
+ mm_log (file_new,ERROR);
+ return 0;
+ }
+ /*(requires that mail be checked whenever cur/ LOCAL->scantime is changed)*/
+ if (sbuf.st_mtime < LOCAL->scantime
+ || !(dir = opendir (file_new)))
+ return 0;
+
+ file_new[dir_len] = '/';
+ memcpy(file_cur, file_new, dir_len-6); /* (-6 to skip "../new") */
+
+ while ((d = readdir (dir))) {
+ /* select only maildir message filename format, regular file,
+ * and skip filenames too long (+3 for ":2,"; +13 for ",S=9999999999";
+ * -6 for removed "../new"; ==> 10)
+ * (skip adding ,S=<size> if size is >= 4 GiB) */
+ if (maildir_select(d)
+ && dir_len + (file_len = strlen(d->d_name)) + 10 < sizeof(file_new)
+ && (memcpy (file_new+dir_len+1,d->d_name,file_len+1),
+ stat(file_new, &sbuf) == 0)
+ && S_ISREG (sbuf.st_mode) ) {
+
+ /* generate target filename, append maildir flags if not present */
+ if (!strstr(d->d_name, ",S=")
+ && sbuf.st_size <= 4294967295U) { /*(space checked above)*/
+ if ((f = strrchr(d->d_name, ':'))
+ && (f[1] == '2' || f[1] == '3') && f[2] == ',') {
+ if (f - d->d_name)
+ memcpy(file_cur+dir_len-6, d->d_name, f - d->d_name);
+ snprintf(file_cur+dir_len-6+(f - d->d_name),
+ file_len - (f - d->d_name) + 14,
+ ",S=%lu%s", sbuf.st_size, f);
+ }
+ else
+ snprintf(file_cur+dir_len-6, file_len + 17,
+ "%s,S=%lu:2,", d->d_name, sbuf.st_size);
+ }
+ else {
+ memcpy(file_cur+dir_len-6, d->d_name, file_len+1);
+ if (!((f = strrchr(d->d_name, ':'))
+ && (f[1] == '2' || f[1] == '3') && f[2] == ','))
+ memcpy(file_cur+dir_len-6+file_len, ":2,", 4);
+ }
+
+ /* move new message from new/ to cur/ */
+ if (rename (file_new,file_cur) == 0) {
+ recent++;
+ }
+ else
+ mm_log("Unable to read new mail!",WARN);
+ }
+ }
+
+ closedir (dir);
+ return recent;
+}
+
+
+static __inline__ void
+maildir_free_elt (MAILSTREAM * const stream, MESSAGECACHE * const elt)
+{
+ if (elt->maildirp)
+ fs_give ((void **) &elt->maildirp);
+ mail_expunged (stream,elt->msgno);
+}
+
+
+
+
+/* check validity of maildir */
+DRIVER *
+maildir_valid (char *mailbox)
+{
+ return maildir_isvalid(mailbox,T) ? &maildirdriver : NIL;
+}
+
+
+/* open maildir */
+MAILSTREAM *
+maildir_open (MAILSTREAM *stream)
+{
+ if (!stream)
+ return &maildirproto;
+
+ /* recycle stream if previously initialized */
+ if (LOCAL) { /* ((LOCAL) is a macro for stream->local with a cast) */
+ maildir_close (stream, 0);
+ stream->dtb = &maildirdriver;
+ mail_free_cache (stream);
+ }
+
+ stream->sequence++;
+ stream->uid_last = 0;
+ stream->uid_validity = 0;
+ stream->nmsgs = stream->recent = 0;
+ stream->local = fs_get (sizeof (MAILDIRLOCAL));
+ /*stream->inbox = is_md_inbox(stream->mailbox);*//* currently unused */
+
+ LOCAL->mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
+ LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
+ LOCAL->dir = cpystr(maildir_path(LOCAL->buf,LOCAL->buflen,stream->mailbox));
+ LOCAL->scantime = 0;
+ LOCAL->last_refresh = 0;
+
+ LOCAL->svc = (const char *) mail_parameters (NIL,GET_SERVICENAME,NIL);
+ if (LOCAL->svc && memcmp(LOCAL->svc, "unknown", 8) == 0)
+ LOCAL->svc = NULL;
+ /* (LOCAL->svc is NULL for PINE client, "imap" for imapd, "pop" for popd) */
+
+ maildir_remove_tmp_old (stream);
+ maildir_ping (stream);
+
+ return stream;
+}
+
+
+/* close maildir */
+void
+maildir_close (MAILSTREAM *stream, long options)
+{
+ MESSAGECACHE *elt;
+ size_t i;
+ mailcache_t const mc = LOCAL->mc;
+ const int expunge = (options & CL_EXPUNGE);
+ size_t file_len;
+ char * const buf = LOCAL->buf;
+ const size_t dir_len = strlen(LOCAL->dir)+1;
+ const size_t buf_len = LOCAL->buflen;
+ const int silent_val = stream->silent;
+
+ stream->silent = T;
+
+ if (dir_len < buf_len) {
+ memcpy(buf, LOCAL->dir, dir_len-1);
+ buf[dir_len-1] = '/';
+ }
+
+ for (i = stream->nmsgs; i != 0; i--) {
+ if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT))) {
+ /*(code here is similar to maildir_expunge(); keep in sync)*/
+ if (expunge && elt->deleted
+ && dir_len+(file_len=strlen(elt->maildirp)) < buf_len
+ && (memcpy(buf+dir_len, elt->maildirp, file_len+1),
+ unlink(buf) == 0)) {
+ }
+ maildir_free_elt (stream,elt);
+ }
+ }
+
+ fs_give ((void **) &LOCAL->dir);
+ fs_give ((void **) &LOCAL->buf);
+ fs_give ((void **) &stream->local); /* nuke the LOCAL data */
+ stream->dtb = NIL; /* log out the DTB */
+
+ stream->silent = silent_val;
+}
+
+
+/* (aims to be more memory-efficient than strcrlfcpy(), though slightly less
+ * flexible, as it requires *dst to be non-NULL, and does not NIL terminate
+ * the *dst string (useful for incremental block expansions rather than
+ * all-at-once))
+ * (d_size must be at least one larger than s_size or dst will immediately
+ * be reallocated; there typically exists at least one newline in s (src))
+ */
+static size_t
+copy_add_CRs (char ** const dst, size_t * const dst_size, size_t offset,
+ char * restrict s, size_t s_size)
+{
+ char *d,*p;
+ size_t d_size = *dst_size - offset;
+
+ d = p = *dst+offset;
+
+ if (offset == 0 && s_size && *s == '\012' && d_size > 1) {
+ s_size--;
+ s++;
+ *p++ = '\015';
+ *p++ = '\012';
+ d_size -= 2;
+ }
+
+ while (s_size && s_size < d_size && (d=memccpy(p,s,'\012',s_size)) != NULL){
+ s_size -= d - p;
+ s += d - p;
+ if (*(s-2) != '\015') {
+ *(d-1) = '\015';
+ *d++ = '\012';
+ }
+ d_size -= d - p;
+ p = d;
+ }
+
+ if (s_size == 0 || d == NULL)
+ return p - *dst + s_size;
+ else if ((offset += (d - *dst)) < SSIZE_MAX && s_size < (SSIZE_MAX>>1)) {
+ /* (if buffer not large enough, recurse once with oversized buffer) */
+ char *tmp = *dst;
+ *dst = (char *)fs_get((*dst_size=offset+(s_size*2))+1);
+ if (offset)
+ memcpy(*dst,tmp,offset);
+ fs_give ((void **) &tmp);
+ return copy_add_CRs(dst, dst_size, offset, s, s_size);
+ }
+ else {
+ mm_fatal("Message way too large. Aborting.");
+ abort();
+ }
+}
+
+
+static size_t
+copy_without_CRs (char * restrict d, char * restrict s, size_t size)
+{
+ char * const orig = d;
+ char *p = d;
+ long len = 0; /* (would prefer ptrdiff_t, but long will do fine) */
+ while ((size -= len) && (d = memccpy(p, s+=len, '\015', size)) != NULL) {
+ len = d - p;
+ p = d - 1;
+ }
+ return p - orig + size;
+}
+
+
+static size_t
+rm_CRs (char * const orig)
+{
+ char *p,*q,*s;
+ long len; /* (would prefer ptrdiff_t, but long will do fine) */
+ if ((p = strchr(orig, '\015')) == NULL)
+ return strlen(orig);
+ for (s = p++; (q = strchr(p,'\015')) != NULL; p = q+1) {
+ if ((len = q - p) != 0) {
+ memmove(s, p, len);
+ s += len;
+ }
+ }
+ if ((len = strlen(p)) != 0)
+ memmove(s, p, len+1);
+ return s - orig + len;
+}
+
+
+/* fetch requested maildir message parts */
+static int
+maildir_fetch_msg_core (MAILSTREAM * const stream, MESSAGECACHE * const elt,
+ const long flags, const int fetch_body)
+{
+ char * const buf = LOCAL->buf;
+ const size_t buflen = LOCAL->buflen;
+ size_t i, rd;
+ ssize_t r;
+ MESSAGE * const restrict msg = &elt->private.msg;
+ int fd = -1;
+ struct stat sbuf;
+
+ if (snprintf(buf,buflen,"%s/%s",LOCAL->dir,elt->maildirp) < buflen
+ && (fd = open(buf, O_RDONLY)) >= 0
+ && fstat (fd, &sbuf) == 0 && !S_ISDIR(sbuf.st_mode)) {
+
+ /* message arrival time (local time of delivery) is mtime of file */
+ /* (this info is for the benefit of PINE mail index sorting code) */
+ ((SORTCACHE *)(*LOCAL->mc)(stream,elt->msgno,CH_SORTCACHE))->arrival =
+ (unsigned long) sbuf.st_mtime;
+
+ i = sbuf.st_size < buflen ? sbuf.st_size : buflen-1;
+ rd = 0;
+ do {
+ r = read(fd, buf+rd, i);
+ } while (r > 0 && (rd += r, i -= r)); /*(allow signals to interrupt)*/
+ /*while (r > 0 ? (rd += r, i -= r) : errno == EINTR); */
+ if (i) {
+ close(fd);
+ return NIL;
+ }
+ buf[rd] = '\0';
+
+ /* find end of message headers, counting newlines along the way */
+ for (i=0, r=0;
+ buf[i] != '\0' && (buf[i] != '\n' || !++r || buf[i+1] != '\n');
+ i++)
+ ;
+ if (buf[i] != '\0') {
+ i += 2;
+ r++;
+ }
+ else {
+ r++; /*(allocate space for at least one newline; none might exist)*/
+ /* should we return NIL?
+ * warn that headers were too long or were incomplete?
+ */
+ }
+
+ if (!msg->header.text.data) {
+ const struct tm * const tm = gmtime (&sbuf.st_mtime);
+
+ /* make plausible IMAPish date string */
+ elt->day = tm->tm_mday;
+ elt->month = tm->tm_mon + 1;
+ elt->year = tm->tm_year + 1900 - BASEYEAR;
+ elt->hours = tm->tm_hour;
+ elt->minutes = tm->tm_min;
+ elt->seconds = tm->tm_sec;
+ elt->zhours = 0;
+ elt->zminutes= 0;
+
+ /* store inode # as unique ID
+ * (assumes all messages in the same folder are from the same mount)
+ * (not used by maildir driver; no maildir_uid() function defined)
+ * (private.uid is unsigned long and so if large file support makes
+ * st_ino a 64-bit quantity, unsigned long better be 64-bits, too)
+ * (Using inode number, there is the possibility that someone else
+ * might delete a message file out from under us, and a new message
+ * file might be created with the same inode number. If this did
+ * happen, the original message should be detected as deleted when
+ * rescanning the directory to initially find the new message that
+ * happened to have the matching inode number. Anyway, since this
+ * maildir driver does not use the unique IDs, this is unlikely to
+ * be a problem)
+ * [should probably add compile-time test and issue warning]
+ * (This code was written before I found out the UIDs are for IMAP.
+ * Using inode is insufficient because it is not necessarily
+ * unique across sessions (the original message could be deleted
+ * and a new message might reuse the same inode). For use with
+ * IMAP, new code should be written that stores UID mapping info
+ * in the filename (before the ':' for flags), or in a static file
+ * or directory, mapping filename (without flags) -- which is
+ * guaranteed to be unique because of the Maildir spec -- to UID.
+ * Simple.)
+ * (For now, this maildir driver is defined with DR_NOSTICKY to
+ * inform the main program that UIDs from this driver are not
+ * persistent across sessions)
+ */
+ elt->private.uid = sbuf.st_ino;
+
+ /* cache message headers */
+ if (flags & FT_INTERNAL) {
+ (msg->header.text.data = fs_get (i+1))[i] = '\0';
+ memcpy(msg->header.text.data, buf, i);
+ msg->header.text.size = i;
+ }
+ else {
+ r += i;
+ msg->header.text.data = fs_get (r+1);
+ msg->header.text.size =
+ copy_add_CRs((char **) &msg->header.text.data,&r,0,buf,i);
+ msg->header.text.data[msg->header.text.size] = '\0';
+ }
+ }
+
+ if (fetch_body) {
+ /* allocate size for body including carriage returns
+ * (generously approx one LF per 16 chars, and then allow for
+ * as many additional LFs as there were chars in original header)
+ */
+ size_t * const dst_size = (size_t *)&msg->text.text.size;
+ char ** const restrict dst = (char **)&msg->text.text.data;
+
+ if (flags & FT_INTERNAL) {
+ *dst_size = sbuf.st_size - i;
+ (*dst = (char *)fs_get(*dst_size+1))[*dst_size] = '\0';
+ if ((r = rd = rd - i))
+ memcpy(*dst, buf+i, r);
+ if ((i = *dst_size - r)) {
+ do {
+ r = read (fd, *dst+rd, i);
+ } while (r > 0 && (rd += r, i -= r)); /*(allow interrupt)*/
+ /* while (r > 0 ? (rd += r, i -= r) : errno == EINTR); */
+ }
+ }
+ else {
+ /*(allocate +1 greater than *dst_size so room for last NIL)*/
+ if (sbuf.st_size < SSIZE_MAX)
+ *dst = (char *)
+ fs_get((*dst_size=sbuf.st_size+(sbuf.st_size>>4))+1);
+ else {
+ close(fd);
+ return NIL;
+ }
+
+ /* expand portion of body in buf that remained after headers */
+ rd = ((r = rd - i)) ? copy_add_CRs(dst,dst_size,0,buf+i,r) : 0;
+
+ /* read message body in blocks, expand, and loop */
+ r++; /*(to get through loop start (r>0), add one to r and i)*/
+ i = sbuf.st_size - i + 1;
+
+ /* while (r > 0 ? (i -= r) : errno == EINTR) */
+ while (r > 0 && (i -= r)) { /*(allows signals to interrupt)*/
+ if ((r = read (fd, buf, i > buflen ? buflen : i)) > 0) {
+ if (i > buflen && (rd+i+(i >> 4)) > *dst_size) {
+ /* attempt to avoid some worst-case allocation by
+ * copy_add_CRs() since it will reallocate as
+ * necessary, but only guaranteed large enough for
+ * block in scope
+ */
+ char *b = *dst;
+ *dst = memcpy(fs_get((*dst_size+=(i*2))+1),b,rd);
+ fs_give((void **) &b);
+ }
+ rd = copy_add_CRs(dst,dst_size,rd,buf,r);
+ }
+ }
+ }
+
+ if (i == 0) {
+ (*dst)[rd] = '\0';
+ *dst_size = rd;
+ elt->rfc822_size = msg->header.text.size + rd;
+ /* size of entire message in RFC822 CRLF form */
+ }
+ else {
+ fs_give((void **) dst);
+ *dst = NULL;
+ *dst_size = 0;
+ close(fd);
+ return NIL;
+ }
+ }
+ else /* headers only */
+ /* approximate RFC822 size; will be updated when body is read */
+ elt->rfc822_size = msg->header.text.size + sbuf.st_size - i;
+
+ close(fd);
+ return T;
+ }
+ else {
+ if (fd != -1)
+ close (fd);
+ else if (errno == ENOENT && maildir_refresh_elt(stream, elt))
+ return maildir_fetch_msg_core(stream, elt, flags, fetch_body);
+ return NIL;
+ }
+}
+
+
+/* fetch maildir message headers */
+char *
+maildir_fetchheader (MAILSTREAM *stream, unsigned long msgno,
+ unsigned long *length, long flags)
+{
+ MESSAGECACHE * const elt =(MESSAGECACHE *)(*LOCAL->mc)(stream,msgno,CH_ELT);
+
+ /*(ignore FT_UID; would have to match against all elts->private.uid first)*/
+ if (elt && !(flags & FT_UID)) {
+
+ SIZEDTEXT * const st = &elt->private.msg.header.text;
+
+ if (st->data
+ || (maildir_fetch_msg_core(stream,elt,flags,0) && st->data)) {
+ *length = st->size;
+ return (char *) st->data;
+ }
+ }
+ *length = 0;
+ return "";
+}
+
+
+/* fetch maildir message body */
+long
+maildir_fetchtext (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
+{
+ MESSAGECACHE * const elt =(MESSAGECACHE *)(*LOCAL->mc)(stream,msgno,CH_ELT);
+
+ /*(ignore FT_UID; would have to match against all elts->private.uid first)*/
+ if (elt && !(flags & FT_UID)) {
+
+ SIZEDTEXT * const st = &elt->private.msg.text.text;
+
+ if (st->data
+ || (maildir_fetch_msg_core(stream,elt,flags,1) && st->data)) {
+ /* mark as seen unless only peeking */
+ if (!(flags & FT_PEEK) && !elt->seen) {
+ elt->seen = T;
+ maildir_flagmsg (stream, elt);
+ MM_FLAGS (stream, msgno);
+ }
+ INIT (bs,mail_string,st->data,st->size);
+ return T;
+ }
+ elt->rfc822_size = 0;
+ INIT (bs,mail_string,"",0);
+ }
+ return NIL;
+}
+
+
+#if 0
+/* The maildir driver uses DR_NOFAST flag to indicate that it does not
+ * implement driver_fetchfast. The following is a possible implementation, but
+ * is slow since it requires opening every single file (one message per file).
+ * If we could obtain all the information from Maildir+ format filenames, then
+ * all of this information could be obtained during maildir_ping() and just
+ * parsed out into appropriate fields here without the need to hit the file
+ * system.
+ */
+void
+maildir_fetchfast (MAILSTREAM *stream, char *sequence, long flags)
+{
+ unsigned long i;
+ MESSAGECACHE *elt;
+ mailcache_t const mc = LOCAL->mc;
+
+ if ((flags & FT_UID)
+ ? mail_uid_sequence (stream,sequence)
+ : mail_sequence (stream,sequence)) {
+ for (i = stream->nmsgs; i != 0; i--) {
+ if ((elt = (MESSAGECACHE *)(*mc)(stream,i,CH_ELT)) && elt->sequence)
+ maildir_fetch_msg_core (stream,elt,flags,0);
+ }
+ }
+}
+#endif
+
+
+/* mail_fetch_structure() does not check return values, so we can not rescan
+ * and update the Maildir at this time. Just attempt to recover if the flags
+ * on a single message were changed. Code flow is copied (yuck) and modified
+ * from maildir_refresh_cur().
+ */
+static int
+maildir_refresh_elt (MAILSTREAM * const stream, MESSAGECACHE * const elt)
+{
+ long i;
+ struct dirent *d;
+ DIR * const dirp = opendir(LOCAL->dir);
+ char * const s = strrchr(elt->maildirp,':');
+ const size_t len = s ? (size_t)(s - elt->maildirp) : strlen(elt->maildirp);
+ if (!dirp) {
+ snprintf (LOCAL->buf, LOCAL->buflen,
+ "Unable to scan maildir: %s", strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ while ((d = readdir(dirp))) {
+ if (memcmp(elt->maildirp,d->d_name,len) == 0 && d->d_name[len] == ':') {
+ /* (if used with an IMAP server which does not use flag
+ * markers ":2," (or ":3,"), then PINE and the IMAP server
+ * may compete back and forth with file renames! If both
+ * sides just append ":2," to the filename, eventually it
+ * will hit NAME_MAX and the file may not be deletable!
+ * In such a case (heretofore not know to this coder), use
+ * PINE in IMAP mode, rather than this maildir driver)
+ */
+ maildir_flagmsg_init (stream,elt,d->d_name);
+ MM_FLAGS (stream,elt->msgno);
+ closedir(dirp);
+ return T;
+ }
+ }
+ closedir(dirp);
+ return NIL;
+}
+
+
+static int
+maildir_refresh_cur (MAILSTREAM * const stream)
+{
+ long i;
+ struct dirent **names = NULL;
+ long nfiles =
+ (long) scandir(LOCAL->dir,&names,maildir_select,maildir_namesort);
+ MESSAGECACHE *elt;
+ mailcache_t const mc = LOCAL->mc;
+ const int silent = stream->silent;
+ long recent = stream->recent;
+ int status = 0;
+ char *s;
+
+ if (nfiles == -1) {
+ snprintf (LOCAL->buf, LOCAL->buflen,
+ "Unable to process maildir: %s", strerror (errno));
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+ if (nfiles > MAXMESSAGES) {
+ snprintf (LOCAL->buf, LOCAL->buflen,
+ "Mailbox has more messages (%lu) exist than maximum (%lu). "
+ "Not all shown.", nfiles, MAXMESSAGES);
+ mm_log (LOCAL->buf,ERROR);
+ /* free *names[] above MAXMESSAGES */
+ for (i = MAXMESSAGES; i < nfiles; i++)
+ fs_give ((void **) &names[i]);
+ nfiles = MAXMESSAGES;
+ }
+
+ if (stream->lock)
+ return NIL;
+ mail_lock (stream); /* locking stream is probably unnecessary */
+
+ LOCAL->last_refresh = time(0);
+
+ /* check that cached filenames match sorted directory contents */
+ i = 0;
+ while (i < stream->nmsgs && i < nfiles) {
+ if ((elt = (MESSAGECACHE *)(*mc)(stream,i+1,CH_ELT))) {
+ if (strcmp(elt->maildirp,names[i]->d_name) == 0)
+ i++; /* matched */
+ else {
+ if ((s = strrchr(elt->maildirp,':'))
+ && memcmp(elt->maildirp, names[i]->d_name,
+ s - elt->maildirp + 1) == 0) {
+ /* (if used with an IMAP server which does not use flag
+ * markers ":2," (or ":3,"), then PINE and the IMAP server
+ * may compete back and forth with file renames! If both
+ * sides just append ":2," to the filename, eventually it
+ * will hit NAME_MAX and the file may not be deletable!
+ * In such a case (heretofore not know to this coder), use
+ * PINE in IMAP mode, rather than this maildir driver)
+ */
+ status |= 1; /* matched, but flags were modified */
+ maildir_flagmsg_init (stream,elt,names[i]->d_name);
+ MM_FLAGS (stream,i+1);
+ i++;
+ }
+ else {
+ status |= 2; /* mismatch */
+ maildir_free_elt (stream, elt);
+ #ifdef PINE_HACK_EXPUNGE
+ if (!stream->silent && LOCAL->svc == NULL)
+ ((PER_STREAM_S *)stream->sparep)->expunge_count--;
+ #endif
+ }
+ }
+ }
+ else { /* create and release this element to force array renumbering */
+ stream->silent = T;
+ (elt = mail_elt (stream, i+1))->maildirp = NULL;
+ maildir_free_elt (stream, elt);
+ stream->silent = silent;
+ }
+ }
+ while (i < stream->nmsgs) {
+ if ((elt = (MESSAGECACHE *)(*mc)(stream,i+1,CH_ELT))) {
+ status |= 2; /* mismatch */
+ maildir_free_elt (stream, elt);
+ #ifdef PINE_HACK_EXPUNGE
+ if (!stream->silent && LOCAL->svc == NULL)
+ ((PER_STREAM_S *)stream->sparep)->expunge_count--;
+ #endif
+ }
+ else { /*create and release this element to force array renumbering*/
+ stream->silent = T;
+ (elt = mail_elt (stream, i+1))->maildirp = NULL;
+ maildir_free_elt (stream, elt);
+ stream->silent = silent;
+ }
+ }
+
+ switch (status) {
+ case 0:
+ break;
+ case 1:
+ #if 0 /* uncomment to send message when flags modified externally */
+ mm_log ("Warning: Message flags have been modified externally.", WARN);
+ #endif
+ break;
+ case 2: case 3: default:
+ mm_log ("Warning: Mailbox changed unexpectedly. Reloading.", WARN);
+ break;
+ }
+
+ i = stream->nmsgs;
+ stream->nmsgs = nfiles;
+ for (; i < nfiles; i++) { /* if newly seen, add to list */
+ mail_exists(stream, i+1);
+ (elt = mail_elt(stream, i+1))->maildirp = NULL;
+ maildir_flagmsg_init (stream, elt, names[i]->d_name); /*fills maildirp*/
+ elt->valid = T;
+ if (!elt->seen) {
+ elt->recent = T;
+ recent++;
+ }
+
+ /* cache message headers so that message sorting by arrival time works
+ * in PINE (This is probably wasteful for other c-client consumers)
+ * Skip if stream->silent is set because it probably means this routine
+ * is being called forward-looking and only the summary results will be
+ * preserved. Specifically, this is skipped for mail_status_default().
+ */
+ if (!silent)
+ maildir_fetch_msg_core (stream, elt, 0L, 0);
+ else if ((s = strstr(elt->maildirp, ",S=")))
+ /* maildir_flagmsg_init() modifies elt->maildirp to add file size.
+ * Use that size here as approximate size of the message. It is not
+ * the size of the message in CRLF form as PINE expects, but will be
+ * corrected when the message is read into memory. (calling
+ * maildir_fetch_msg_core() above fills approx elt->rfc822_size)
+ */
+ elt->rfc822_size = strtoul(s+3, (char **)NULL, 10);
+ }
+
+ mail_unlock (stream);
+
+ for (i = 0; i < nfiles; i++) /* free the names stuff */
+ fs_give ((void **) &names[i]);
+ if (names) {
+ struct dirent *** const namestmp = &names;
+ fs_give ((void **) namestmp);
+ }
+
+ mail_exists (stream,nfiles);
+ mail_recent (stream,recent);
+
+ return T;
+}
+
+
+static int
+maildir_stat_cur (MAILSTREAM * const stream, struct stat * const sbuf)
+{
+ /*get last modify/change times (also quick sanity check for valid maildir)*/
+ if (stat (LOCAL->dir,sbuf) == 0 && S_ISDIR (sbuf->st_mode))
+ return T;
+ else {
+ char tmp[MAILTMPLEN];
+ snprintf (tmp,sizeof(tmp),"Unable to open maildir: %s",strerror(errno));
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+}
+
+
+static int
+maildir_ping_cur (MAILSTREAM * const stream, const long recent)
+{
+ struct stat st;
+
+ if (!maildir_stat_cur (stream,&st))
+ return NIL;
+
+ if (LOCAL->scantime < st.st_mtime
+ || recent
+ || LOCAL->last_refresh + 900 < time(0)) { /* (refresh if > 15 mins) */
+ if (maildir_refresh_cur (stream))
+ LOCAL->scantime = st.st_mtime;
+ else
+ return NIL;
+ }
+ return T;
+
+}
+
+
+long
+maildir_ping (MAILSTREAM *stream)
+{
+ /* (similar to maildir_check() but with additional check for new mail) */
+ return maildir_ping_cur (stream, maildir_move_new (stream));
+}
+
+
+void
+maildir_check (MAILSTREAM *stream)
+{
+ /* (similar to maildir_ping(), but without new mail check) */
+ if (maildir_ping_cur (stream, 0))
+ mm_log ("Check completed",(long) NIL);
+}
+
+
+void
+maildir_flagmsg (MAILSTREAM *stream, MESSAGECACHE *elt)
+{
+ if (stream->silent)
+ maildir_flagmsg_rename (stream, elt);
+ else {
+ struct stat st;
+ int renamed;
+ long recent = NIL;
+
+ if (!maildir_stat_cur (stream,&st))
+ st.st_mtime = 0; /* ignore error */
+
+ if ((renamed = maildir_flagmsg_rename (stream, elt)))
+ recent = maildir_move_new (stream);
+ /* LOCAL->scantime (last scantime of cur/) is also compared to new/,
+ * and so new mail must be checked whenever LOCAL->scantime is updated*/
+
+ if (recent || LOCAL->scantime < st.st_mtime) {
+ if (maildir_refresh_cur (stream))
+ LOCAL->scantime = renamed ? time(0) : st.st_mtime;
+ }
+ else if (renamed)
+ LOCAL->scantime = time(0);
+ }
+}
+
+
+long
+maildir_create (MAILSTREAM *stream, char *mailbox)
+{
+ char path[MAILTMPLEN];
+ char *s;
+ size_t len;
+
+ maildir_path (path,sizeof(path),mailbox);
+
+ if (access (path,F_OK) == 0) {
+ snprintf (path, sizeof(path),
+ "Can't create mailbox %s: mailbox already exists", mailbox);
+ mm_log (path,ERROR);
+ return NIL;
+ }
+
+ /* remove trailing "/cur" from maildir directory path */
+ len = strlen (path) - 4;
+ path[len] = '\0';
+
+ /* check that parent directory exists */
+ if ((s = strrchr(path, '/'))) {
+ *s = '\0';
+ if (access (path,W_OK) == 0)
+ *s = '/';
+ else {
+ *s = '/';
+ /* create nested hierarchy of dirs (brute force; could be better) */
+ for (s = path; (s = strchr(s, '/')); s++) {
+ *s = '\0';
+ if (mkdir (path, (DIR_MASK)) != 0 && errno != EEXIST) {
+ char err[MAILTMPLEN];
+ snprintf (err, sizeof(err),
+ "Can't create mailbox %s: %s (%s)",
+ mailbox, path, strerror (errno));
+ mm_log (err,ERROR);
+ return NIL;
+ }
+ *s = '/';
+ }
+ }
+ }
+
+ /* create maildir and its subdirectories */
+ if (mkdir (path, (DIR_MASK)) == 0
+ && (memcpy (path+len, "/tmp", 5), mkdir (path, (DIR_MASK)) == 0)
+ && (memcpy (path+len, "/new", 5), mkdir (path, (DIR_MASK)) == 0)
+ && (memcpy (path+len, "/cur", 5), mkdir (path, (DIR_MASK)) == 0))
+ return T;
+ else {
+ char err[MAILTMPLEN];
+ snprintf (err, sizeof(err), "Can't create mailbox %s: %s (%s)",
+ mailbox, path, strerror (errno));
+ mm_log (err,ERROR);
+ return NIL;
+ }
+
+ return T;
+}
+
+
+void
+maildir_expunge (MAILSTREAM *stream)
+{
+ MESSAGECACHE *elt;
+ char * const buf = LOCAL->buf;
+ unsigned long i, n = 0, recent = stream->recent;
+ mailcache_t const mc = LOCAL->mc;
+ size_t file_len;
+ const size_t dir_len = strlen(LOCAL->dir)+1;
+ const size_t buf_len = LOCAL->buflen;
+ struct stat st;
+
+ if (dir_len < buf_len) {
+ memcpy(buf, LOCAL->dir, dir_len-1);
+ buf[dir_len-1] = '/';
+ }
+ else
+ return;
+
+ if (!maildir_stat_cur (stream,&st))
+ st.st_mtime = 0; /* ignore error */
+
+ for (i = stream->nmsgs; i != 0; i--) {
+ if (!(elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) || !elt->deleted)
+ continue;
+ /*(code here is similar to expunge in maildir_close(); keep in sync)*/
+ if (dir_len+(file_len=strlen(elt->maildirp)) < buf_len
+ && (memcpy(buf+dir_len, elt->maildirp, file_len+1),
+ unlink(buf) == 0)) {
+ if (elt->recent)
+ --recent; /* if recent, one less recent message */
+ n++; /* count up one more expunged message */
+ maildir_free_elt (stream,elt);
+ }
+ else {
+ snprintf (buf, buf_len,"Expunge of message %ld failed, aborted: %s",
+ i, strerror (errno));
+ mm_log (buf,WARN);
+ break;
+ }
+ }
+
+ if (n) {
+ /* notify upper level of new mailbox size and send message to user */
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,recent);
+ snprintf (LOCAL->buf, LOCAL->buflen, "Expunged %ld messages", n);
+ mm_log (LOCAL->buf,(long) NIL);
+
+ if (!stream->silent) {
+ /* unlink() causes directory st_mtime to be updated
+ * LOCAL->scantime (last scantime of cur/) is also compared to new/,
+ * and so new mail must be checked when LOCAL->scantime is updated*/
+ if (maildir_move_new (stream) || LOCAL->scantime < st.st_mtime) {
+ if (maildir_refresh_cur (stream))
+ LOCAL->scantime = time(0);
+ }
+ else
+ LOCAL->scantime = time(0);
+ }
+ }
+ else
+ mm_log ("No messages deleted, so no update needed",(long) NIL);
+}
+
+
+static void
+maildir_getflags(MESSAGECACHE * const elt, const char * const flags)
+{
+ char *t,tmp[MAILTMPLEN];
+ size_t n;
+
+ elt->seen = elt->answered = elt->flagged = elt->deleted = elt->draft = NIL;
+
+ /* check for valid flags string */
+ if (flags == NULL || (n = strlen(flags)) == 0 || n > sizeof(tmp)-2)
+ return;
+ if (*flags == '(') {
+ if (flags[n-1] != ')') {
+ mm_log ("Bad flag list",ERROR);
+ return;
+ }
+ if ((n -= 2) == 0)
+ return;
+ memcpy(tmp, flags+1, n);
+ }
+ else
+ memcpy(tmp, flags, n);
+ /* add trailing space, terminate, uppercase string (for simple comparison)*/
+ tmp[n] = ' ';
+ tmp[n+1] = '\0';
+ t = ucase (tmp);
+
+ /* parse flag strings, e.g. '\Seen \Flagged ...' or '(\Seen \Flagged ...)'*/
+ while (*t) {
+ if (*t == '\\') {
+ switch (*++t) {
+ case 'S':
+ if (0==memcmp(t,"SEEN ", 5)) {elt->seen = T; t+=5;} break;
+ case 'A':
+ if (0==memcmp(t,"ANSWERED ",9)) {elt->answered= T; t+=9;} break;
+ case 'F':
+ if (0==memcmp(t,"FLAGGED ", 8)) {elt->flagged = T; t+=8;} break;
+ case 'D':
+ if (*(t+1) == 'E' &&
+ 0==memcmp(t,"DELETED ", 8)) {elt->deleted = T; t+=8;}
+ else if (0==memcmp(t,"DRAFT ",6)){elt->draft = T; t+=6;}
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ char err[MAILTMPLEN];
+ n = strcspn(t, " ");
+ snprintf (err,sizeof(err),"Unknown flag: %.*s",(n < 80 ? n : 80),t);
+ mm_log (err,ERROR);
+ t += n + 1;
+ }
+ }
+}
+
+
+static long
+maildir_append_msg (MAILSTREAM * const stream,
+ char * const restrict mailbox,
+ MESSAGECACHE * const elt, /* contains message flags */
+ const time_t unix_time,
+ char * restrict message,
+ size_t size)
+{
+ /*(Note: MAILSTREAM *stream might not be an open stream! (uninitialized!))*/
+ ssize_t w = 0;
+ int fd = -1;
+ char path_tmp[MAILTMPLEN],path_cur[MAILTMPLEN],*m;
+ const size_t dir_len =
+ strlen(maildir_path(path_tmp, sizeof(path_tmp), mailbox));
+ struct timeval tv;
+ struct stat st;
+ struct utimbuf utbuf;
+ static size_t transact = 0; /* intentionally 'static' */
+
+ (void)gettimeofday(&tv, NULL);
+ utbuf.actime = utbuf.modtime = unix_time;
+
+ if (dir_len == 0
+ || snprintf(path_tmp+dir_len, sizeof(path_tmp) - dir_len,
+ "/../tmp/%lu.M%luP%lu_%07lu.%s,S=%lu",
+ (unsigned long) tv.tv_sec, (unsigned long) tv.tv_usec,
+ (unsigned long) getpid (), (unsigned long) transact++,
+ mylocalhost (), (unsigned long) size)
+ >= sizeof(path_tmp) - dir_len)
+ return NIL;
+ memcpy(path_cur,path_tmp,dir_len);
+
+ if ((fd = open (path_tmp,O_WRONLY|O_CREAT|O_EXCL|O_SYNC,(FILE_MASK))) >= 0
+ && fstat (fd,&st) == 0
+ && (w = snprintf(path_cur+dir_len, sizeof(path_cur) - dir_len,
+ "/%010lu.M%06luP%luV%lxI%lx_%07lu.%s,S=%lu:2,",
+ (unsigned long) tv.tv_sec, (unsigned long) tv.tv_usec,
+ (unsigned long) getpid (), (unsigned long) st.st_dev,
+ (unsigned long) st.st_ino, (unsigned long) transact-1,
+ mylocalhost (), (unsigned long) size))
+ < sizeof(path_cur) - dir_len - MAILDIR_MAX_FLAGS) {
+
+ if (elt->maildirp
+ && (m = strrchr(elt->maildirp,':'))
+ && (m[1] == '2' || m[1] == '3') && m[2] == ',')
+ m += 3;
+ else
+ m = NULL;
+ maildir_add_flags(path_cur+dir_len+w, MAILDIR_MAX_FLAGS+1, elt, m);
+ /* remove the trashed 'T' flag if present */
+ if ((m = strchr(path_cur+dir_len+w,'T'))) {
+ if (*(m+1))
+ memmove(m,m+1,strlen(m)); /*(also moves terminating NIL)*/
+ else
+ *m = '\0';
+ }
+
+ do {
+ w = write (fd, message, size);
+ } while (w > 0 ? (message += w, size -= w) : errno == EINTR);
+
+ if (size == 0 && fsync(fd) == 0 && close(fd) == 0
+ && (fd = -1, utime(path_tmp,&utbuf) == 0)
+ && link(path_tmp,path_cur) == 0) {
+ unlink (path_tmp);
+ return LONGT; /* success */
+ }
+ else {
+ if (fd != -1)
+ close(fd);
+ unlink (path_tmp);
+ snprintf (path_tmp, sizeof(path_tmp), "Message append failed: %s",
+ strerror (errno));
+ mm_log (path_tmp,ERROR);
+ return NIL;
+ }
+ }
+ else {
+ if (fd != -1) {
+ close(fd);
+ unlink (path_tmp);
+ if (w >= sizeof(path_cur) - dir_len - MAILDIR_MAX_FLAGS)
+ return NIL; /* filename too long */
+ }
+ else if (errno == EEXIST) {
+ sleep(2);
+ return maildir_append_msg (stream, mailbox, elt, unix_time,
+ message, size);
+ }
+ snprintf (path_tmp, sizeof(path_tmp), "Can't open append mailbox: %s",
+ strerror (errno));
+ mm_log (path_tmp,ERROR);
+ return NIL;
+ }
+}
+
+
+long
+maildir_append (MAILSTREAM *stream, char *mailbox, append_t af, void *data)
+{
+ /*(Note: MAILSTREAM *stream is not an open stream! It is uninitialized!)*/
+ char *message,*flags,*date,tmp[MAILTMPLEN];
+ size_t size = 0;
+ STRING *s;
+ char ** const msgtmp = &message; /*(avoid fs_give() compiler -Wall warn)*/
+ time_t unix_time;
+ MESSAGECACHE elt = {0}; /* initializes all elements to 0, incl maildirp */
+ long status = T;
+
+ if (!maildir_isvalid (mailbox, NIL)) {
+ snprintf (tmp, sizeof(tmp), "Not a valid Maildir mailbox: %s", mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ /*(MM_APPEND(af)(...) is callback function to retrieve next message)*/
+ while (status && (status=(MM_APPEND(af)(stream,data,&flags,&date,&s)))&& s){
+
+ maildir_getflags(&elt, flags);
+
+ /* (if PINE provided an interface to the SORTCACHE s->arrival time
+ * of the originating message, that time would be used instead) */
+ /* wasteful, but uses existing routines */
+ if (!date)
+ rfc822_date ((date = tmp));
+ if (mail_parse_date (&elt,date))
+ unix_time = mail_longdate(&elt);
+ else {
+ char err[MAILTMPLEN];
+ snprintf(err, sizeof(err), "Bad date in append: %.80s", date);
+ mm_log (err,ERROR);
+ unix_time = time(0); /* just use current time */
+ }
+
+ /* copy data without carriage returns */
+ size = SIZE (s);
+ (message = (char *) fs_get (size+1))[size] = '\0';
+ size = copy_without_CRs(message, s->curpos, size);
+
+ status = maildir_append_msg(stream,mailbox,&elt,unix_time,message,size);
+
+ fs_give ((void **) msgtmp); /* release the buffer */
+ }
+
+ return status;
+}
+
+
+long
+maildir_copy (MAILSTREAM *stream, char *sequence, char *mailbox, long options)
+{
+ char *message;
+ struct stat sbuf;
+ MESSAGECACHE *elt;
+ mailcache_t const mc = LOCAL->mc;
+ char ** const msgtmp = &message; /*(avoid fs_give() compiler -Wall warn)*/
+ int fd = -1;
+ long i,status = T;
+ size_t size;
+
+ if (!maildir_isvalid (mailbox, NIL)) {
+ snprintf (LOCAL->buf, LOCAL->buflen, "Not a valid Maildir mailbox: %s",
+ mailbox);
+ mm_log (LOCAL->buf,ERROR);
+ return NIL;
+ }
+
+ if (!((options & CP_UID)
+ ? mail_uid_sequence (stream, sequence)
+ : mail_sequence (stream,sequence) ))
+ return NIL;
+
+ for (i = stream->nmsgs; i != 0; i--) {
+ /* marked for copy? or else continue to next message */
+ if (!(elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) || !elt->sequence)
+ continue;
+
+ if (snprintf (LOCAL->buf,LOCAL->buflen,"%s/%s",LOCAL->dir,
+ elt->maildirp) < LOCAL->buflen
+ && (fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0
+ && fstat (fd,&sbuf) == 0) {
+
+ /* maildir_copy() does not appear to be used by current applications
+ * based on the c-client library. If it were, some possible
+ * performance enhancements to this routine might be worthwhile:
+ * Best, we could try hard linking the files between folders, and
+ * could use same filename and intentionally skip if it already
+ * exists (to prevent duplicating message into target mailbox), and
+ * could fall back to other methods if folders were located on
+ * different filesystems. Failing that, we could check to see if
+ * we already have the message headers and body in memory
+ * (elt->private.msg.{header,text}.text.data) and could copy that
+ * and then rm_CRs() on the copy. And if we did not have the
+ * message in memory, we could alternatively use FT_INTERNAL flag
+ * with maildir_fetchtext(), but then would have to free it because
+ * the rest of PINE expects the data in CRLF format */
+
+ ssize_t r; /* slurp message */
+ size = 0;
+ (message = (char *) fs_get (sbuf.st_size+1))[sbuf.st_size] = '\0';
+ do {
+ r = read (fd, message+size, sbuf.st_size - size);
+ } while (r > 0 ? (size += r) != sbuf.st_size : errno == EINTR);
+
+ /* no need to check elt->valid to verify that flags are valid
+ * because if we have a maildir elt, then elt->valid and flags
+ * were set from the filename when the elt was created. */
+
+ if (size == sbuf.st_size) {
+ close (fd);
+ size = rm_CRs(message); /* remove carriage returns */
+ /* add message to maildir */
+ status = maildir_append_msg (stream,mailbox,elt,sbuf.st_mtime,
+ message,size);
+ fs_give ((void **) msgtmp);
+ if (!status)
+ break;
+ }
+ else {
+ snprintf (LOCAL->buf, LOCAL->buflen,
+ "Skipping message %ld; copy failed: %s",
+ i, strerror (errno));
+ mm_log (LOCAL->buf,WARN);
+ fs_give ((void **) msgtmp);
+ close (fd); /* close after strerror(errno)*/
+ }
+ }
+ else {
+ snprintf (LOCAL->buf, LOCAL->buflen,
+ "Skipping message %ld; copy failed: %s",
+ i, strerror (errno));
+ mm_log (LOCAL->buf,WARN);
+ if (fd != -1)
+ close(fd);
+ }
+ }
+
+ return status;
+}
+
+
+long
+maildir_delete (MAILSTREAM *stream, char *mailbox)
+{
+ char *name,tmp[MAILTMPLEN];
+ size_t dir_len,file_len;
+ struct dirent *d;
+ DIR *dirp;
+ char *subdir_names[] = {"cur","new","tmp",NULL};
+ int i;
+
+ if (!maildir_isvalid (mailbox,NIL)) {
+ snprintf (tmp, sizeof(tmp), "Can't delete mailbox %s: no such mailbox",
+ mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+
+ dir_len = strlen (maildir_path (tmp,sizeof(tmp),mailbox)) - 3;
+ if (*tmp == '\0')
+ return NIL;
+ if (stream && LOCAL && strcmp (tmp, LOCAL->dir) == 0)
+ maildir_close(stream, 0);
+
+ for (i = 0; subdir_names[i]; i++) {
+ memcpy(tmp+dir_len, subdir_names[i], 4);
+ if ((dirp = opendir (tmp))) {
+ tmp[dir_len+3] = '/';
+ while ((d = readdir (dirp))) {
+ file_len = strlen((name = d->d_name));
+ if ((file_len > 2 /* skip "." and ".." dirs */
+ || !(name[0] == '.'
+ && (name[1]=='\0' ||(name[1]=='.' && name[2]=='\0'))))
+ && dir_len + 4 + file_len < sizeof(tmp)) {
+ memcpy(tmp+dir_len+4, name, file_len+1);
+ unlink (tmp);
+ }
+ }
+ closedir (dirp);
+ }
+ tmp[dir_len+3] = '\0';
+ rmdir (tmp);
+ }
+ tmp[dir_len-1] = '\0';
+ if (rmdir (tmp) == 0)
+ return T;
+ else {
+ snprintf (tmp, sizeof(tmp), "Can't delete mailbox %s, errors "
+ "encountered removing files", mailbox);
+ mm_log (tmp,ERROR);
+ return NIL;
+ }
+}
+
+
+long
+maildir_rename (MAILSTREAM *stream, char *oldname, char *newname)
+{
+ char new_path[MAILTMPLEN],old_path[MAILTMPLEN],*cur;
+
+ /* old mailbox must be valid, and new mailbox must not already exist */
+ if (!maildir_isvalid (oldname,NIL)) {
+ snprintf (new_path, sizeof(new_path),
+ "Can't rename mailbox %s: no such mailbox", oldname);
+ mm_log (new_path,ERROR);
+ return NIL;
+ }
+
+ maildir_path (new_path,sizeof(new_path),newname);
+ if ((cur = strrchr(new_path,'/')) && memcmp(cur,"/cur",5) == 0)
+ *cur = '\0';
+
+ if (access (new_path,F_OK) == 0) {
+ snprintf (new_path, sizeof(new_path), "Can't rename to mailbox %s: "
+ "destination already exists", newname);
+ mm_log (new_path,ERROR);
+ return NIL;
+ }
+
+ maildir_path (old_path,sizeof(old_path),oldname);
+ if ((cur = strrchr(old_path,'/')) && memcmp(cur,"/cur",5) == 0)
+ *cur = '\0';
+
+ if (rename (old_path, new_path) != 0) {
+ snprintf (new_path,sizeof(new_path),"Can't rename mailbox %s to %s: %s",
+ oldname, newname, strerror (errno));
+ mm_log (new_path,ERROR);
+ return NIL;
+ }
+
+ if (stream && LOCAL) {
+ *cur = '/';
+ if (strcmp (old_path, LOCAL->dir) == 0) {
+ fs_give ((void **) &LOCAL->dir);
+ LOCAL->dir = cpystr (old_path);
+ }
+ }
+
+ return T;
+}
+
+
+#if 0
+long
+maildir_sub (MAILSTREAM *stream, char *mailbox)
+{
+ return sm_subscribe (mailbox);
+}
+
+
+long
+maildir_unsub (MAILSTREAM *stream, char *mailbox)
+{
+ return sm_unsubscribe (mailbox);
+}
+#endif
+
+
+/*
+ * XXX: maildir_lsub() and maildir_list() are incomplete
+ * See http://www.math.washington.edu/~chappa/pine/info/maildir.html patch
+ * or similar code in mh.c, but do not copy sloppy coding (e.g. strcpy())
+ */
+
+void
+maildir_lsub (MAILSTREAM *stream, char *ref, char *pat)
+{
+ void *sdb = NIL;
+ char *s;
+
+ /* cycle through subscriptions and match against canonical form of name */
+ while ((s = sm_read (&sdb))) {
+ if (pmatch_full (s,pat,'/'))
+ mm_lsub (stream,'/',s,NIL);
+ }
+}
+
+
+void
+maildir_list (MAILSTREAM *stream,char *ref, char *pat)
+{
+ if (pat && is_md_inbox(pat)) {
+ mm_list (stream,NIL,"INBOX",LATT_NOINFERIORS);
+ }
+}
+
+
+void *
+maildir_parameters (long function,void *value)
+{
+ return NULL;
+}
--- /dev/null
+++ alpine-1.0+dfsg/imap/src/osdep/unix/maildir.h
@@ -0,0 +1,51 @@
+/*
+ * Please read maildir.c for license and information
+ *
+ */
+#ifndef C_CLIENT_MAILDIR_H
+#define C_CLIENT_MAILDIR_H
+
+
+#define MAILDIRPATH "Maildir"
+
+typedef struct maildir_local {
+ mailcache_t mc; /* mailcache function */
+ const char *svc; /* servicename of program using c-client lib */
+ char *dir; /* mail directory name */
+ char *buf; /* temporary buffer */
+ unsigned long buflen; /* current size of temporary buffer */
+ time_t scantime; /* last time directory scanned */
+ time_t last_refresh; /* last time directory actually refreshed */
+} MAILDIRLOCAL;
+
+/* Convenient access to local data */
+
+#define LOCAL ((MAILDIRLOCAL *) stream->local)
+
+/* Function prototypes */
+
+DRIVER *maildir_valid (char *mailbox);
+MAILSTREAM *maildir_open (MAILSTREAM *stream);
+void maildir_gc (MAILSTREAM *stream,long gcflags);
+void maildir_close (MAILSTREAM *stream, long options);
+long maildir_ping (MAILSTREAM *stream);
+void maildir_check (MAILSTREAM *stream);
+long maildir_fetchtext (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
+char *maildir_fetchheader (MAILSTREAM *stream,unsigned long msgno,
+ unsigned long *length, long flags);
+void maildir_fetchfast (MAILSTREAM *stream,char *sequence,long flags);
+void maildir_list (MAILSTREAM *stream,char *ref,char *pat);
+void *maildir_parameters (long function,void *value);
+long maildir_create (MAILSTREAM *stream,char *mailbox);
+void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
+void maildir_expunge (MAILSTREAM *stream);
+long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
+long maildir_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
+long maildir_delete (MAILSTREAM *stream,char *mailbox);
+long maildir_rename (MAILSTREAM *stream,char *oldname,char *newname);
+long maildir_sub (MAILSTREAM *stream,char *mailbox);
+long maildir_unsub (MAILSTREAM *stream,char *mailbox);
+void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat);
+
+
+#endif /* C_CLIENT_MAILDIR_H */
--- alpine-1.0+dfsg.orig/imap/src/osdep/unix/Makefile
+++ alpine-1.0+dfsg/imap/src/osdep/unix/Makefile
@@ -27,7 +27,7 @@
# Command line build parameters
EXTRAAUTHENTICATORS=
-EXTRADRIVERS=mbox
+EXTRADRIVERS=maildir mbox
PASSWDTYPE=std
SSLTYPE=nopwd
IP=4
@@ -144,7 +144,7 @@
# However, mh needs to be before any sysinbox formats (such as mmdf or unix)
# since otherwise INBOX won't work correctly when mh_allow_inbox is set.
#
-DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile
+DEFAULTDRIVERS=maildir imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile
CHUNKSIZE=65536
# Normally no need to change any of these
@@ -153,7 +153,7 @@
BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \
dummy.o pseudo.o netmsg.o flstring.o fdstring.o \
rfc822.o nntp.o smtp.o imap4r1.o pop3.o \
- unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o
+ unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o maildir.o
CFLAGS=-g
CAT=cat
@@ -892,6 +892,7 @@
unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h
utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c
utf8aux.o: mail.h misc.h osdep.h utf8.h
+maildir.o: mail.h misc.h osdep.h maildir.h dummy.h
# OS-dependent
--- alpine-1.0+dfsg.orig/pith/sort.c
+++ alpine-1.0+dfsg/pith/sort.c
@@ -128,7 +128,9 @@
raw_current = mn_m2raw(msgmap, mn_get_cur(msgmap));
- if(new_sort == SortArrival){
+ /*(do not bypass SortArrival sorting; needed for maildir arrival sorting) */
+ /* if(new_sort == SortArrival){ */
+ if(0){
/*
* NOTE: RE c-client sorting, our idea of arrival is really
* just the natural sequence order. C-client, and probably
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment