diff -ruN pine4.61/imap/src/c-client/mail.h pine4.61-maildir/imap/src/c-client/mail.h --- pine4.61/imap/src/c-client/mail.h 2004-06-21 21:06:58.000000000 -0400 +++ pine4.61-maildir/imap/src/c-client/mail.h 2004-11-20 19:25:22.588588752 -0500 @@ -785,6 +785,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; diff -ruN pine4.61/imap/src/osdep/unix/Makefile pine4.61-maildir/imap/src/osdep/unix/Makefile --- pine4.61/imap/src/osdep/unix/Makefile 2004-06-22 16:56:05.000000000 -0400 +++ pine4.61-maildir/imap/src/osdep/unix/Makefile 2004-11-20 19:25:22.589588600 -0500 @@ -21,7 +21,7 @@ # Command line build parameters EXTRAAUTHENTICATORS= -EXTRADRIVERS=mbox +EXTRADRIVERS=maildir mbox PASSWDTYPE=std SSLTYPE=nopwd IP=4 @@ -107,7 +107,7 @@ # Standard distribution build parameters DEFAULTAUTHENTICATORS=md5 pla log -DEFAULTDRIVERS=imap nntp pop3 mh mx mbx tenex mtx mmdf unix news phile +DEFAULTDRIVERS=maildir imap nntp pop3 mh mx mbx tenex mtx mmdf unix news phile # Normally no need to change any of these @@ -116,7 +116,7 @@ BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.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 + unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o maildir.o CFLAGS=-g CAT=cat @@ -834,7 +834,7 @@ tenex.o: mail.h misc.h osdep.h dummy.h unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h utf8.o: mail.h misc.h osdep.h utf8.h - +maildir.o: mail.h misc.h osdep.h maildir.h dummy.h # OS-dependent diff -ruN pine4.61/imap/src/osdep/unix/dummy.c pine4.61-maildir/imap/src/osdep/unix/dummy.c --- pine4.61/imap/src/osdep/unix/dummy.c 2004-02-02 19:50:32.000000000 -0500 +++ pine4.61-maildir/imap/src/osdep/unix/dummy.c 2004-11-20 19:25:22.589588600 -0500 @@ -362,14 +362,18 @@ char *s,tmp[MAILTMPLEN]; /* 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 = dummy_file (tmp,name)) && !stat (s,&sbuf) && (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 diff -ruN pine4.61/imap/src/osdep/unix/maildir.c pine4.61-maildir/imap/src/osdep/unix/maildir.c --- pine4.61/imap/src/osdep/unix/maildir.c 1969-12-31 19:00:00.000000000 -0500 +++ pine4.61-maildir/imap/src/osdep/unix/maildir.c 2004-11-20 19:28:06.394686440 -0500 @@ -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 /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 + * Version: 21.07.98 + * + * Please read the README.maildir file before using this module! + * If you have any questions, please e-mail ml@techno.org + * Multiple inboxes patch by Dean Gaudet + * + * ================================================= + * + * Based on the IMAP2 maildir routines by: + * + * Author: Eric Green + * Bloodhounds International Inc. + * thrytis@imaxx.net + * + * Additional contributions from: + * Aidas Kasparas (kaspar@soften.ktu.lt) + * + * 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: MRC@CAC.Washington.EDU + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + #include "../../pico/estruct.h" + #include "../../pico/pico.h" + #include "../../pine/pine.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= 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; +} diff -ruN pine4.61/imap/src/osdep/unix/maildir.h pine4.61-maildir/imap/src/osdep/unix/maildir.h --- pine4.61/imap/src/osdep/unix/maildir.h 1969-12-31 19:00:00.000000000 -0500 +++ pine4.61-maildir/imap/src/osdep/unix/maildir.h 2004-11-20 19:25:22.592588144 -0500 @@ -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 */ diff -ruN pine4.61/pine/mailcmd.c pine4.61-maildir/pine/mailcmd.c --- pine4.61/pine/mailcmd.c 2004-07-13 21:35:15.000000000 -0400 +++ pine4.61-maildir/pine/mailcmd.c 2004-11-20 19:25:22.597587384 -0500 @@ -3699,8 +3699,7 @@ * the destination folder will need... */ context_apply(tmp, context, save_folder, sizeof(tmp)); - save_stream = (stream->dtb->flags & DR_LOCAL) && !IS_REMOTE(tmp) ? - stream : context_same_stream(context, save_folder, stream); + save_stream = context_same_stream(context, save_folder, stream); } /* if needed, this'll get set in mm_notify */ diff -ruN pine4.61/pine/mailindx.c pine4.61-maildir/pine/mailindx.c --- pine4.61/pine/mailindx.c 2004-06-14 17:59:15.000000000 -0400 +++ pine4.61-maildir/pine/mailindx.c 2004-11-20 19:25:22.601586776 -0500 @@ -7469,7 +7469,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 diff -ruN pine4.61/pine/pine.h pine4.61-maildir/pine/pine.h --- pine4.61/pine/pine.h 2004-07-14 20:25:12.000000000 -0400 +++ pine4.61-maildir/pine/pine.h 2004-11-20 19:25:22.604586320 -0500 @@ -63,7 +63,7 @@ #ifndef _PINE_INCLUDED #define _PINE_INCLUDED -#define PINE_VERSION "4.61" +#define PINE_VERSION "4.61L0" #define PHONE_HOME_VERSION "-count" #define PHONE_HOME_HOST "docserver.cac.washington.edu"