4e25fd59c1
so far, we checked for M_DEAD only in loops over messages. but we should have checked srec->msg uses as well. this would make the code a mess, so instead call back from the drivers when messages are expunged, so we can reset the pointers. the only case where this really matters so far is the flag setting loop, which may cause the concurrent expunge of not yet handled messages to be detected by the maildir driver.
1951 lines
52 KiB
C
1951 lines
52 KiB
C
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
|
|
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
|
|
// SPDX-FileCopyrightText: 2004 Theodore Y. Ts'o <tytso@mit.edu>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
|
|
/*
|
|
* mbsync - mailbox synchronizer
|
|
*/
|
|
|
|
#define DEBUG_FLAG DEBUG_MAILDIR
|
|
|
|
#include "driver.h"
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <utime.h>
|
|
|
|
#if !defined(_POSIX_SYNCHRONIZED_IO) || _POSIX_SYNCHRONIZED_IO <= 0
|
|
# define fdatasync fsync
|
|
#endif
|
|
|
|
#ifdef USE_DB
|
|
#include <db.h>
|
|
#endif /* USE_DB */
|
|
|
|
#define SUB_UNSET 0
|
|
#define SUB_VERBATIM 1
|
|
#define SUB_MAILDIRPP 2
|
|
#define SUB_LEGACY 3
|
|
|
|
typedef union maildir_store_conf {
|
|
store_conf_t gen;
|
|
struct {
|
|
STORE_CONF
|
|
char *path;
|
|
char *inbox;
|
|
#ifdef USE_DB
|
|
int alt_map;
|
|
#endif /* USE_DB */
|
|
char info_delimiter;
|
|
char sub_style;
|
|
char failed;
|
|
char *info_prefix, *info_stop; /* precalculated from info_delimiter */
|
|
};
|
|
} maildir_store_conf_t;
|
|
|
|
typedef union maildir_message {
|
|
message_t gen;
|
|
struct {
|
|
MESSAGE(union maildir_message)
|
|
char *base;
|
|
};
|
|
} maildir_message_t;
|
|
|
|
typedef union maildir_store {
|
|
store_t gen;
|
|
struct {
|
|
STORE(union maildir_store)
|
|
int uvfd, uvok, is_inbox, fresh[3];
|
|
uint opts, minuid, maxuid, finduid, pairuid, newuid, uidvalidity, nuid;
|
|
uint_array_t excs;
|
|
char *path; // own
|
|
char *trash;
|
|
#ifdef USE_DB
|
|
DB *db;
|
|
char *usedb;
|
|
#endif /* USE_DB */
|
|
string_list_t *boxes; // _list results
|
|
char listed; // was _list already run with these flags?
|
|
// note that the message counts do _not_ reflect stats from msgs,
|
|
// but mailbox totals. also, don't trust them beyond the initial load.
|
|
int total_msgs, recent_msgs;
|
|
maildir_message_t *msgs;
|
|
wakeup_t lcktmr;
|
|
|
|
void (*expunge_callback)( message_t *msg, void *aux );
|
|
void (*bad_callback)( void *aux );
|
|
void *callback_aux;
|
|
};
|
|
} maildir_store_t;
|
|
|
|
#ifdef USE_DB
|
|
static DBT key, value; /* no need to be reentrant, and this saves lots of memset()s */
|
|
#endif /* USE_DB */
|
|
static struct flock lck;
|
|
|
|
static int MaildirCount;
|
|
|
|
static uchar
|
|
maildir_parse_flags( const char *info_prefix, const char *base )
|
|
{
|
|
const char *s;
|
|
uint i;
|
|
uchar flags;
|
|
|
|
flags = 0;
|
|
if ((s = strstr( base, info_prefix )))
|
|
for (s += 3, i = 0; i < as(MsgFlags); i++)
|
|
if (strchr( s, MsgFlags[i] ))
|
|
flags |= (1 << i);
|
|
return flags;
|
|
}
|
|
|
|
static int
|
|
maildir_ensure_path( maildir_store_conf_t *conf )
|
|
{
|
|
if (!conf->path) {
|
|
error( "Maildir error: store '%s' has no Path\n", conf->name );
|
|
conf->failed = FAIL_FINAL;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Subdirs of INBOX include a leading slash. */
|
|
/* Path includes a trailing slash, Inbox does not. */
|
|
static char *
|
|
maildir_join_path( maildir_store_conf_t *conf, int in_inbox, const char *box )
|
|
{
|
|
char *out, *p;
|
|
const char *prefix;
|
|
uint pl, bl, n;
|
|
char c;
|
|
|
|
if (in_inbox || conf->sub_style == SUB_MAILDIRPP) {
|
|
prefix = conf->inbox;
|
|
} else {
|
|
if (maildir_ensure_path( conf ) < 0)
|
|
return NULL;
|
|
prefix = conf->path;
|
|
}
|
|
pl = strlen( prefix );
|
|
for (bl = 0, n = 0; (c = box[bl]); bl++) {
|
|
if (c == '/') {
|
|
if (conf->sub_style == SUB_UNSET) {
|
|
error( "Maildir error: accessing subfolder '%s', but store '%s' does not specify SubFolders style\n",
|
|
box, conf->name );
|
|
return NULL;
|
|
}
|
|
n++;
|
|
} else if (c == '.' && conf->sub_style == SUB_MAILDIRPP) {
|
|
error( "Maildir error: store '%s', folder '%s': SubFolders style Maildir++ does not support dots in mailbox names\n",
|
|
conf->name, box );
|
|
return NULL;
|
|
}
|
|
}
|
|
switch (conf->sub_style) {
|
|
case SUB_VERBATIM:
|
|
n = 0;
|
|
break;
|
|
case SUB_MAILDIRPP:
|
|
n = 2;
|
|
break;
|
|
default: /* SUB_LEGACY and SUB_UNSET */
|
|
break;
|
|
}
|
|
out = nfmalloc( pl + bl + n + 1 );
|
|
memcpy( out, prefix, pl );
|
|
p = out + pl;
|
|
if (conf->sub_style == SUB_MAILDIRPP) {
|
|
*p++ = '/';
|
|
*p++ = '.';
|
|
}
|
|
while ((c = *box++)) {
|
|
if (c == '/') {
|
|
switch (conf->sub_style) {
|
|
case SUB_VERBATIM:
|
|
*p++ = c;
|
|
break;
|
|
case SUB_LEGACY:
|
|
*p++ = c;
|
|
FALLTHROUGH
|
|
default: /* SUB_MAILDIRPP */
|
|
*p++ = '.';
|
|
break;
|
|
}
|
|
} else {
|
|
*p++ = c;
|
|
}
|
|
}
|
|
*p = 0;
|
|
return out;
|
|
}
|
|
|
|
static int
|
|
maildir_validate_path( maildir_store_conf_t *conf )
|
|
{
|
|
struct stat st;
|
|
|
|
if (stat( conf->path, &st ) || !S_ISDIR(st.st_mode)) {
|
|
error( "Maildir error: cannot open store '%s'\n", conf->path );
|
|
conf->failed = FAIL_FINAL;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void lcktmr_timeout( void *aux );
|
|
|
|
static store_t *
|
|
maildir_alloc_store( store_conf_t *gconf, const char *label ATTR_UNUSED )
|
|
{
|
|
maildir_store_t *ctx;
|
|
|
|
ctx = nfzalloc( sizeof(*ctx) );
|
|
ctx->driver = &maildir_driver;
|
|
ctx->gen.conf = gconf;
|
|
ctx->uvfd = -1;
|
|
init_wakeup( &ctx->lcktmr, lcktmr_timeout, ctx );
|
|
return &ctx->gen;
|
|
}
|
|
|
|
static void
|
|
maildir_connect_store( store_t *gctx,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_store_conf_t *conf = ctx->conf;
|
|
|
|
if (conf->path && maildir_validate_path( conf ) < 0) {
|
|
cb( DRV_STORE_BAD, aux );
|
|
return;
|
|
}
|
|
if (conf->trash && !(ctx->trash = maildir_join_path( conf, 0, conf->trash ))) {
|
|
cb( DRV_STORE_BAD, aux );
|
|
return;
|
|
}
|
|
cb( DRV_OK, aux );
|
|
}
|
|
|
|
static void
|
|
free_maildir_messages( maildir_message_t *msg )
|
|
{
|
|
while (msg) {
|
|
maildir_message_t *tmsg = msg->next;
|
|
free( msg->base );
|
|
free( msg->msgid );
|
|
free( msg );
|
|
msg = tmsg;
|
|
}
|
|
}
|
|
|
|
static void
|
|
maildir_cleanup( store_t *gctx )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
free_maildir_messages( ctx->msgs );
|
|
#ifdef USE_DB
|
|
if (ctx->db)
|
|
ctx->db->close( ctx->db, 0 );
|
|
free( ctx->usedb );
|
|
#endif /* USE_DB */
|
|
free( ctx->path );
|
|
free( ctx->excs.data );
|
|
if (ctx->uvfd >= 0)
|
|
close( ctx->uvfd );
|
|
conf_wakeup( &ctx->lcktmr, -1 );
|
|
}
|
|
|
|
static void
|
|
maildir_free_store( store_t *gctx )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
maildir_cleanup( gctx );
|
|
wipe_wakeup( &ctx->lcktmr );
|
|
free( ctx->trash );
|
|
free_string_list( ctx->boxes );
|
|
free( gctx );
|
|
}
|
|
|
|
static void
|
|
maildir_cleanup_drv( void )
|
|
{
|
|
}
|
|
|
|
static void
|
|
maildir_set_callbacks( store_t *gctx, void (*exp_cb)( message_t *, void * ),
|
|
void (*bad_cb)( void * ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
ctx->expunge_callback = exp_cb;
|
|
ctx->bad_callback = bad_cb;
|
|
ctx->callback_aux = aux;
|
|
}
|
|
|
|
static void
|
|
maildir_invoke_bad_callback( maildir_store_t *ctx )
|
|
{
|
|
ctx->bad_callback( ctx->callback_aux );
|
|
}
|
|
|
|
static int
|
|
maildir_list_maildirpp( maildir_store_t *ctx, int flags, const char *inbox )
|
|
{
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
int warned = 0;
|
|
struct stat st;
|
|
|
|
if (ctx->listed & LIST_PATH) // Implies LIST_INBOX
|
|
return 0;
|
|
|
|
if (!(ctx->listed & LIST_INBOX))
|
|
add_string_list( &ctx->boxes, "INBOX" );
|
|
|
|
char path[_POSIX_PATH_MAX];
|
|
int pathLen = nfsnprintf( path, _POSIX_PATH_MAX, "%s/", inbox );
|
|
if (!(dir = opendir( path ))) {
|
|
if (errno == ENOENT || errno == ENOTDIR)
|
|
return 0;
|
|
sys_error( "Maildir error: cannot list %s", path );
|
|
return -1;
|
|
}
|
|
while ((de = readdir( dir ))) {
|
|
const char *ent = de->d_name;
|
|
if (*ent++ != '.' || !*ent)
|
|
continue;
|
|
char name[_POSIX_PATH_MAX];
|
|
char *effName = name;
|
|
if (*ent == '.') {
|
|
if (ctx->listed & LIST_INBOX)
|
|
continue;
|
|
if (!*++ent)
|
|
continue;
|
|
// The Maildir++ Inbox is technically not under Path (as there is none), so
|
|
// "*" would never match INBOX*, which is rather unintuitive. Matching INBOX*
|
|
// implicitly instead makes it consistent with an IMAP Store with an empty Path.
|
|
} else {
|
|
if (!(flags & (LIST_PATH | LIST_PATH_MAYBE)))
|
|
continue;
|
|
// Explained in maildir_list_recurse().
|
|
if (starts_with( ent, -1, "INBOX", 5 ) && (!ent[5] || ent[5] == '.')) {
|
|
if (!warned) {
|
|
warned = 1;
|
|
path[pathLen] = 0;
|
|
warn( "Maildir warning: ignoring INBOX in %s\n", path );
|
|
}
|
|
continue;
|
|
}
|
|
effName += 6;
|
|
}
|
|
nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s/cur", de->d_name );
|
|
if (!stat( path, &st ) && S_ISDIR(st.st_mode)) {
|
|
int nl = nfsnprintf( name, _POSIX_PATH_MAX, "INBOX/%s", ent );
|
|
for (int i = 6; i < nl; i++) {
|
|
if (name[i] == '.')
|
|
name[i] = '/';
|
|
}
|
|
add_string_list( &ctx->boxes, effName );
|
|
}
|
|
}
|
|
closedir (dir);
|
|
|
|
if (flags & (LIST_PATH | LIST_PATH_MAYBE))
|
|
ctx->listed |= LIST_PATH;
|
|
ctx->listed |= LIST_INBOX;
|
|
return 0;
|
|
}
|
|
|
|
static int maildir_list_inbox( maildir_store_t *ctx, int flags, const char *basePath );
|
|
static int maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox );
|
|
|
|
static int
|
|
maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags, int depth,
|
|
const char *inbox, uint inboxLen, const char *basePath, uint basePathLen,
|
|
char *path, int pathLen, char *name, int nameLen )
|
|
{
|
|
DIR *dir;
|
|
int style = ctx->conf->sub_style;
|
|
int pl, nl;
|
|
struct dirent *de;
|
|
struct stat st;
|
|
|
|
if (!(dir = opendir( path ))) {
|
|
if (isBox && (errno == ENOENT || errno == ENOTDIR))
|
|
return 0;
|
|
sys_error( "Maildir error: cannot list %s", path );
|
|
return -1;
|
|
}
|
|
if (isBox > 1 && style == SUB_UNSET) {
|
|
error( "Maildir error: found subfolder '%.*s', but store '%s' does not specify SubFolders style\n",
|
|
nameLen - 1, name, ctx->conf->name );
|
|
closedir( dir );
|
|
return -1;
|
|
}
|
|
if (++depth > 10) {
|
|
// We do the other checks first to avoid confusing error messages for files.
|
|
error( "Maildir error: path %s is too deeply nested. Symlink loop?\n", path );
|
|
closedir( dir );
|
|
return -1;
|
|
}
|
|
while ((de = readdir( dir ))) {
|
|
const char *ent = de->d_name;
|
|
if (ent[0] == '.' && (!ent[1] || (ent[1] == '.' && !ent[2])))
|
|
continue;
|
|
pl = nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent );
|
|
if (pl == 3 && (!memcmp( ent, "cur", 3 ) || !memcmp( ent, "new", 3 ) || !memcmp( ent, "tmp", 3 )))
|
|
continue;
|
|
pl += pathLen;
|
|
if (inbox && equals( path, pl, inbox, inboxLen )) {
|
|
// Inbox nested into Path.
|
|
if (maildir_list_inbox( ctx, flags, NULL ) < 0) {
|
|
closedir( dir );
|
|
return -1;
|
|
}
|
|
} else if (basePath && equals( path, pl, basePath, basePathLen )) {
|
|
// Path nested into Inbox.
|
|
if (maildir_list_path( ctx, flags, NULL ) < 0) {
|
|
closedir( dir );
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (style == SUB_LEGACY) {
|
|
if (*ent == '.') {
|
|
if (!isBox)
|
|
continue;
|
|
ent++;
|
|
} else {
|
|
if (isBox)
|
|
continue;
|
|
}
|
|
}
|
|
// A folder named "INBOX" would be indistinguishable from the
|
|
// actual INBOX after prefix stripping, so drop it. This applies
|
|
// only to the fully uppercased spelling, as our canonical box
|
|
// names are case-sensitive (unlike IMAP's INBOX).
|
|
if (!nameLen && equals( ent, -1, "INBOX", 5 )) {
|
|
path[pathLen] = 0;
|
|
warn( "Maildir warning: ignoring INBOX in %s\n", path );
|
|
continue;
|
|
}
|
|
nl = nameLen + nfsnprintf( name + nameLen, _POSIX_PATH_MAX - nameLen, "%s", ent );
|
|
path[pl++] = '/';
|
|
nfsnprintf( path + pl, _POSIX_PATH_MAX - pl, "cur" );
|
|
if (!stat( path, &st ) && S_ISDIR(st.st_mode))
|
|
add_string_list( &ctx->boxes, name );
|
|
path[pl] = 0;
|
|
name[nl++] = '/';
|
|
if (maildir_list_recurse( ctx, isBox + 1, flags, depth, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) {
|
|
closedir( dir );
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
closedir (dir);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
maildir_list_inbox( maildir_store_t *ctx, int flags, const char *basePath )
|
|
{
|
|
char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX];
|
|
|
|
if (ctx->listed & LIST_INBOX)
|
|
return 0;
|
|
ctx->listed |= LIST_INBOX;
|
|
|
|
add_string_list( &ctx->boxes, "INBOX" );
|
|
return maildir_list_recurse(
|
|
ctx, 1, flags, 0, NULL, 0, basePath, basePath ? strlen( basePath ) - 1 : 0,
|
|
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s/", ctx->conf->inbox ),
|
|
name, nfsnprintf( name, _POSIX_PATH_MAX, "INBOX/" ) );
|
|
}
|
|
|
|
static int
|
|
maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox )
|
|
{
|
|
char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX];
|
|
|
|
if (ctx->listed & LIST_PATH)
|
|
return 0;
|
|
ctx->listed |= LIST_PATH;
|
|
|
|
if (maildir_ensure_path( ctx->conf ) < 0)
|
|
return -1;
|
|
return maildir_list_recurse(
|
|
ctx, 0, flags, 0, inbox, inbox ? strlen( inbox ) : 0, NULL, 0,
|
|
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s", ctx->conf->path ),
|
|
name, 0 );
|
|
}
|
|
|
|
static void
|
|
maildir_list_store( store_t *gctx, int flags,
|
|
void (*cb)( int sts, string_list_t *boxes, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_store_conf_t *conf = ctx->conf;
|
|
|
|
if (conf->sub_style == SUB_MAILDIRPP
|
|
? maildir_list_maildirpp( ctx, flags, conf->inbox ) < 0
|
|
: ((((flags & LIST_PATH) || ((flags & LIST_PATH_MAYBE) && conf->path))
|
|
&& maildir_list_path( ctx, flags, conf->inbox ) < 0) ||
|
|
((flags & LIST_INBOX)
|
|
&& maildir_list_inbox( ctx, flags, conf->path ) < 0))) {
|
|
maildir_invoke_bad_callback( ctx );
|
|
cb( DRV_CANCELED, NULL, aux );
|
|
} else {
|
|
cb( DRV_OK, ctx->boxes, aux );
|
|
}
|
|
}
|
|
|
|
static const char *subdirs[] = { "cur", "new", "tmp" };
|
|
|
|
typedef struct {
|
|
char *base;
|
|
char *msgid;
|
|
uint size;
|
|
uint uid;
|
|
uchar recent;
|
|
char tuid[TUIDL];
|
|
} msg_t;
|
|
|
|
DEFINE_ARRAY_TYPE(msg_t)
|
|
|
|
static void
|
|
maildir_free_scan( msg_t_array_alloc_t *msglist )
|
|
{
|
|
uint i;
|
|
|
|
if (msglist->array.data) {
|
|
for (i = 0; i < msglist->array.size; i++) {
|
|
free( msglist->array.data[i].base );
|
|
free( msglist->array.data[i].msgid );
|
|
}
|
|
free( msglist->array.data );
|
|
}
|
|
}
|
|
|
|
#define _24_HOURS (3600 * 24)
|
|
|
|
static int
|
|
maildir_clear_tmp( char *buf, int bufsz, int bl )
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *entry;
|
|
time_t now;
|
|
struct stat st;
|
|
|
|
memcpy( buf + bl, "tmp/", 5 );
|
|
bl += 4;
|
|
if (!(dirp = opendir( buf ))) {
|
|
sys_error( "Maildir error: cannot list %s", buf );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
time( &now );
|
|
while ((entry = readdir( dirp ))) {
|
|
nfsnprintf( buf + bl, bufsz - bl, "%s", entry->d_name );
|
|
if (stat( buf, &st )) {
|
|
if (errno != ENOENT)
|
|
sys_error( "Maildir error: cannot access %s", buf );
|
|
} else if (S_ISREG(st.st_mode) && now - st.st_ctime >= _24_HOURS) {
|
|
/* This should happen infrequently enough that it won't be
|
|
* bothersome to the user to display when it occurs.
|
|
*/
|
|
notice( "Maildir notice: removing stale file %s\n", buf );
|
|
if (unlink( buf ) && errno != ENOENT)
|
|
sys_error( "Maildir error: cannot remove %s", buf );
|
|
}
|
|
}
|
|
closedir( dirp );
|
|
return DRV_OK;
|
|
}
|
|
|
|
static int
|
|
maildir_validate( const char *box, int create, maildir_store_t *ctx )
|
|
{
|
|
int i, bl, ret;
|
|
struct stat st;
|
|
char buf[_POSIX_PATH_MAX];
|
|
|
|
bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", box );
|
|
if (stat( buf, &st )) {
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot access mailbox '%s'", buf );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if (!create)
|
|
return DRV_BOX_BAD;
|
|
if (mkdir_p( buf, bl - 1 )) {
|
|
sys_error( "Maildir error: cannot create mailbox '%s'", buf );
|
|
ctx->conf->failed = FAIL_FINAL;
|
|
maildir_invoke_bad_callback( ctx );
|
|
return DRV_CANCELED;
|
|
}
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
notdir:
|
|
error( "Maildir error: '%s' is no valid mailbox\n", buf );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
for (i = 0; i < 3; i++) {
|
|
memcpy( buf + bl, subdirs[i], 4 );
|
|
if (stat( buf, &st )) {
|
|
/* We always create new/ and tmp/ if they are missing. cur/ is the presence indicator. */
|
|
if (!i && !create)
|
|
return DRV_BOX_BAD;
|
|
if (mkdir( buf, 0700 )) {
|
|
sys_error( "Maildir error: cannot create directory %s", buf );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
ctx->fresh[i] = 1;
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
goto notdir;
|
|
} else {
|
|
if (i == 2) {
|
|
if ((ret = maildir_clear_tmp( buf, sizeof(buf), bl )) != DRV_OK)
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return DRV_OK;
|
|
}
|
|
|
|
#ifdef USE_DB
|
|
static void
|
|
make_key( const char *info_stop, DBT *tkey, const char *name )
|
|
{
|
|
char *u = strpbrk( name, info_stop );
|
|
DIAG_PUSH
|
|
DIAG_DISABLE("-Wcast-qual") // C has no const_cast<> ...
|
|
tkey->data = (char *)name;
|
|
DIAG_POP
|
|
tkey->size = u ? (size_t)(u - name) : strlen( name );
|
|
}
|
|
#endif /* USE_DB */
|
|
|
|
static int
|
|
maildir_store_uidval( maildir_store_t *ctx )
|
|
{
|
|
int n;
|
|
#ifdef USE_DB
|
|
int ret;
|
|
uint uv[2];
|
|
#endif
|
|
char buf[128];
|
|
|
|
#ifdef USE_DB
|
|
if (ctx->db) {
|
|
key.data = (void *)"UIDVALIDITY";
|
|
key.size = 11;
|
|
uv[0] = ctx->uidvalidity;
|
|
uv[1] = ctx->nuid;
|
|
value.data = uv;
|
|
value.size = sizeof(uv);
|
|
if ((ret = ctx->db->put( ctx->db, NULL, &key, &value, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->put()" );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if ((ret = ctx->db->sync( ctx->db, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->sync()" );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
} else
|
|
#endif /* USE_DB */
|
|
{
|
|
n = sprintf( buf, "%u\n%u\n", ctx->uidvalidity, ctx->nuid );
|
|
lseek( ctx->uvfd, 0, SEEK_SET );
|
|
if (write( ctx->uvfd, buf, (uint)n ) != n || ftruncate( ctx->uvfd, n ) || (UseFSync && fdatasync( ctx->uvfd ))) {
|
|
error( "Maildir error: cannot write UIDVALIDITY in %s\n", ctx->path );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
}
|
|
conf_wakeup( &ctx->lcktmr, 2 );
|
|
return DRV_OK;
|
|
}
|
|
|
|
static int
|
|
maildir_init_uidval( maildir_store_t *ctx )
|
|
{
|
|
ctx->uidvalidity = (uint)time( NULL );
|
|
ctx->nuid = 0;
|
|
ctx->uvok = 0;
|
|
#ifdef USE_DB
|
|
if (ctx->db) {
|
|
u_int32_t count;
|
|
ctx->db->truncate( ctx->db, NULL, &count, 0 );
|
|
}
|
|
#endif /* USE_DB */
|
|
return maildir_store_uidval( ctx );
|
|
}
|
|
|
|
static int
|
|
maildir_init_uidval_new( maildir_store_t *ctx )
|
|
{
|
|
notice( "Maildir notice: no UIDVALIDITY in %s, creating new.\n", ctx->path );
|
|
return maildir_init_uidval( ctx );
|
|
}
|
|
|
|
static int
|
|
maildir_uidval_lock( maildir_store_t *ctx )
|
|
{
|
|
int n;
|
|
#ifdef USE_DB
|
|
int ret;
|
|
struct stat st;
|
|
#endif
|
|
char buf[128];
|
|
|
|
if (pending_wakeup( &ctx->lcktmr )) {
|
|
/* The unlock timer is active, so we are obviously already locked. */
|
|
return DRV_OK;
|
|
}
|
|
/* This (theoretically) works over NFS. Let's hope nobody else did
|
|
the same in the opposite order, as we'd deadlock then. */
|
|
#if SEEK_SET != 0
|
|
lck.l_whence = SEEK_SET;
|
|
#endif
|
|
lck.l_type = F_WRLCK;
|
|
if (fcntl( ctx->uvfd, F_SETLKW, &lck )) {
|
|
error( "Maildir error: cannot fcntl lock UIDVALIDITY in %s.\n", ctx->path );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
if (fstat( ctx->uvfd, &st )) {
|
|
sys_error( "Maildir error: cannot fstat UID database in %s", ctx->path );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if (db_create( &ctx->db, NULL, 0 )) {
|
|
fputs( "Maildir error: db_create() failed\n", stderr );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if ((ret = (ctx->db->open)( ctx->db, NULL, ctx->usedb, NULL, DB_HASH,
|
|
st.st_size ? 0 : DB_CREATE | DB_TRUNCATE, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->open(%s)", ctx->usedb );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
key.data = (void *)"UIDVALIDITY";
|
|
key.size = 11;
|
|
if ((ret = ctx->db->get( ctx->db, NULL, &key, &value, 0 ))) {
|
|
if (ret != DB_NOTFOUND) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->get()" );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
return maildir_init_uidval_new( ctx );
|
|
}
|
|
ctx->uidvalidity = ((uint *)value.data)[0];
|
|
ctx->nuid = ((uint *)value.data)[1];
|
|
} else
|
|
#endif
|
|
{
|
|
lseek( ctx->uvfd, 0, SEEK_SET );
|
|
if ((n = read( ctx->uvfd, buf, sizeof(buf) - 1 )) <= 0 ||
|
|
(buf[n] = 0, sscanf( buf, "%u\n%u", &ctx->uidvalidity, &ctx->nuid ) != 2)) {
|
|
#if 1
|
|
/* In a generic driver, resetting the UID validity would be the right thing.
|
|
* But this would mess up the sync state completely. So better bail out and
|
|
* give the user a chance to fix the mailbox. */
|
|
if (n) {
|
|
error( "Maildir error: cannot read UIDVALIDITY in %s.\n", ctx->path );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
#endif
|
|
return maildir_init_uidval_new( ctx );
|
|
}
|
|
}
|
|
ctx->uvok = 1;
|
|
conf_wakeup( &ctx->lcktmr, 2 );
|
|
return DRV_OK;
|
|
}
|
|
|
|
static void
|
|
maildir_uidval_unlock( maildir_store_t *ctx )
|
|
{
|
|
#ifdef USE_DB
|
|
if (ctx->db) {
|
|
ctx->db->close( ctx->db, 0 );
|
|
ctx->db = NULL;
|
|
}
|
|
#endif /* USE_DB */
|
|
lck.l_type = F_UNLCK;
|
|
fcntl( ctx->uvfd, F_SETLK, &lck );
|
|
}
|
|
|
|
static void
|
|
lcktmr_timeout( void *aux )
|
|
{
|
|
maildir_uidval_unlock( (maildir_store_t *)aux );
|
|
}
|
|
|
|
static int
|
|
maildir_obtain_uid( maildir_store_t *ctx, uint *uid )
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = maildir_uidval_lock( ctx )) != DRV_OK)
|
|
return ret;
|
|
*uid = ++ctx->nuid;
|
|
return maildir_store_uidval( ctx );
|
|
}
|
|
|
|
#ifdef USE_DB
|
|
static int
|
|
maildir_set_uid( maildir_store_t *ctx, const char *name, uint *uid )
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = maildir_uidval_lock( ctx )) != DRV_OK)
|
|
return ret;
|
|
*uid = ++ctx->nuid;
|
|
|
|
make_key( ctx->conf->info_stop, &key, name );
|
|
value.data = uid;
|
|
value.size = sizeof(*uid);
|
|
if ((ret = ctx->db->put( ctx->db, NULL, &key, &value, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->put()" );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
return maildir_store_uidval( ctx );
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
maildir_compare( const void *l, const void *r )
|
|
{
|
|
const msg_t *lm = (const msg_t *)l, *rm = (const msg_t *)r;
|
|
char *ldot, *rdot, *ldot2, *rdot2, *lseq, *rseq;
|
|
uint llen, rlen;
|
|
int ret;
|
|
|
|
if (lm->uid != rm->uid) // Can't subtract, the result might not fit into signed int.
|
|
return lm->uid > rm->uid ? 1 : -1;
|
|
|
|
/* No UID, so sort by arrival date. We should not do this, but we rely
|
|
on the suggested unique file name scheme - we have no choice. */
|
|
/* The first field are always the seconds. Alphabetical sort should be
|
|
faster than numeric. */
|
|
if (!(ldot = strchr( lm->base, '.' )) || !(rdot = strchr( rm->base, '.' )))
|
|
goto stronly; /* Should never happen ... */
|
|
llen = (uint)(ldot - lm->base), rlen = (uint)(rdot - rm->base);
|
|
/* The shorter number is smaller. Really. This won't trigger with any
|
|
mail created after Sep 9 2001 anyway. */
|
|
if ((ret = (int)llen - (int)rlen))
|
|
return ret;
|
|
if ((ret = memcmp( lm->base, rm->base, llen )))
|
|
return ret;
|
|
|
|
ldot++, rdot++;
|
|
|
|
if ((llen = strtoul( ldot, &ldot2, 10 ))) {
|
|
if (!(rlen = strtoul( rdot, &rdot2, 10 )))
|
|
goto stronly; /* Comparing apples to oranges ... */
|
|
/* Classical PID specs */
|
|
if ((ret = (int)llen - (int)rlen)) {
|
|
retpid:
|
|
/* Handle PID wraparound. This works only on systems
|
|
where PIDs are not reused too fast */
|
|
if (ret > 20000 || ret < -20000)
|
|
ret = -ret;
|
|
return ret;
|
|
}
|
|
return (*ldot2 != '_' ? 0 : atoi( ldot2 + 1 )) -
|
|
(*rdot2 != '_' ? 0 : atoi( rdot2 + 1 ));
|
|
}
|
|
|
|
if (!(ldot2 = strchr( ldot, '.' )) || !(rdot2 = strchr( rdot, '.' )))
|
|
goto stronly; /* Should never happen ... */
|
|
llen = (uint)(ldot2 - ldot), rlen = (uint)(rdot2 - rdot);
|
|
|
|
if (((lseq = memchr( ldot, '#', llen )) && (rseq = memchr( rdot, '#', rlen ))) ||
|
|
((lseq = memchr( ldot, 'M', llen )) && (rseq = memchr( rdot, 'M', rlen ))))
|
|
return atoi( lseq + 1 ) - atoi( rseq + 1 );
|
|
|
|
if ((lseq = memchr( ldot, 'P', llen )) && (rseq = memchr( rdot, 'P', rlen ))) {
|
|
if ((ret = atoi( lseq + 1 ) - atoi( rseq + 1 )))
|
|
goto retpid;
|
|
if ((lseq = memchr( ldot, 'Q', llen )) && (rseq = memchr( rdot, 'Q', rlen )))
|
|
return atoi( lseq + 1 ) - atoi( rseq + 1 );
|
|
}
|
|
|
|
stronly:
|
|
/* Fall-back, so the sort order is defined at all */
|
|
return strcmp( lm->base, rm->base );
|
|
}
|
|
|
|
static int
|
|
maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
|
|
{
|
|
maildir_store_conf_t *conf = ctx->conf;
|
|
DIR *d;
|
|
FILE *f;
|
|
struct dirent *e;
|
|
const char *u, *ru;
|
|
#ifdef USE_DB
|
|
DB *tdb;
|
|
DBC *dbc;
|
|
#endif /* USE_DB */
|
|
msg_t *entry;
|
|
uint i;
|
|
int bl, fnl, ret;
|
|
uint uid;
|
|
time_t now, stamps[2];
|
|
struct stat st;
|
|
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
|
|
|
|
again:
|
|
ARRAY_INIT( msglist );
|
|
ctx->total_msgs = ctx->recent_msgs = 0;
|
|
if (ctx->uvok || ctx->maxuid == UINT_MAX) {
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
if (db_create( &tdb, NULL, 0 )) {
|
|
fputs( "Maildir error: db_create() failed\n", stderr );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if ((tdb->open)( tdb, NULL, NULL, NULL, DB_HASH, DB_CREATE, 0 )) {
|
|
fputs( "Maildir error: tdb->open() failed\n", stderr );
|
|
bork:
|
|
tdb->close( tdb, 0 );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
}
|
|
#endif /* USE_DB */
|
|
bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", ctx->path );
|
|
restat:
|
|
now = time( NULL );
|
|
for (i = 0; i < 2; i++) {
|
|
memcpy( buf + bl, subdirs[i], 4 );
|
|
if (stat( buf, &st )) {
|
|
sys_error( "Maildir error: cannot stat %s", buf );
|
|
goto dfail;
|
|
}
|
|
if (st.st_mtime == now && !(DFlags & ZERODELAY) && !ctx->fresh[i]) {
|
|
/* If the modification happened during this second, we wouldn't be able to
|
|
* tell if there were further modifications during this second. So wait.
|
|
* This has the nice side effect that we wait for "batches" of changes to
|
|
* complete. On the downside, it can potentially block indefinitely. */
|
|
notice( "Maildir notice: sleeping due to recent directory modification.\n" );
|
|
sleep( 1 ); /* FIXME: should make this async */
|
|
goto restat;
|
|
}
|
|
stamps[i] = st.st_mtime;
|
|
}
|
|
for (i = 0; i < 2; i++) {
|
|
memcpy( buf + bl, subdirs[i], 4 );
|
|
if (!(d = opendir( buf ))) {
|
|
sys_error( "Maildir error: cannot list %s", buf );
|
|
rfail:
|
|
maildir_free_scan( msglist );
|
|
dfail:
|
|
#ifdef USE_DB
|
|
if (ctx->usedb)
|
|
tdb->close( tdb, 0 );
|
|
#endif /* USE_DB */
|
|
return DRV_BOX_BAD;
|
|
}
|
|
while ((e = readdir( d ))) {
|
|
if (*e->d_name == '.')
|
|
continue;
|
|
ctx->total_msgs++;
|
|
ctx->recent_msgs += i;
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
if (maildir_uidval_lock( ctx ) != DRV_OK)
|
|
goto mbork;
|
|
make_key( conf->info_stop, &key, e->d_name );
|
|
if ((ret = ctx->db->get( ctx->db, NULL, &key, &value, 0 ))) {
|
|
if (ret != DB_NOTFOUND) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->get()" );
|
|
mbork:
|
|
maildir_free_scan( msglist );
|
|
closedir( d );
|
|
goto bork;
|
|
}
|
|
uid = UINT_MAX;
|
|
} else {
|
|
value.size = 0;
|
|
if ((ret = tdb->put( tdb, NULL, &key, &value, 0 ))) {
|
|
tdb->err( tdb, ret, "Maildir error: tdb->put()" );
|
|
goto mbork;
|
|
}
|
|
uid = *(uint *)value.data;
|
|
}
|
|
} else
|
|
#endif /* USE_DB */
|
|
{
|
|
uid = (ctx->uvok && (u = strstr( e->d_name, ",U=" ))) ? strtoul( u + 3, NULL, 10 ) : 0;
|
|
if (!uid)
|
|
uid = UINT_MAX;
|
|
}
|
|
if (uid <= ctx->maxuid) {
|
|
if (uid < ctx->minuid && !find_uint_array( ctx->excs, uid ))
|
|
continue;
|
|
entry = msg_t_array_append( msglist );
|
|
entry->base = nfstrdup( e->d_name );
|
|
entry->msgid = NULL;
|
|
entry->uid = uid;
|
|
entry->recent = (uchar)i;
|
|
entry->size = 0;
|
|
entry->tuid[0] = 0;
|
|
}
|
|
}
|
|
closedir( d );
|
|
}
|
|
for (i = 0; i < 2; i++) {
|
|
memcpy( buf + bl, subdirs[i], 4 );
|
|
if (stat( buf, &st )) {
|
|
sys_error( "Maildir error: cannot re-stat %s", buf );
|
|
goto rfail;
|
|
}
|
|
if (st.st_mtime != stamps[i]) {
|
|
/* Somebody messed with the mailbox since we started listing it. */
|
|
#ifdef USE_DB
|
|
if (ctx->usedb)
|
|
tdb->close( tdb, 0 );
|
|
#endif /* USE_DB */
|
|
maildir_free_scan( msglist );
|
|
goto again;
|
|
}
|
|
}
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
if (maildir_uidval_lock( ctx ) != DRV_OK) {
|
|
} else if ((ret = ctx->db->cursor( ctx->db, NULL, &dbc, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->cursor()" );
|
|
} else {
|
|
for (;;) {
|
|
if ((ret = dbc->c_get( dbc, &key, &value, DB_NEXT ))) {
|
|
if (ret != DB_NOTFOUND)
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->c_get()" );
|
|
break;
|
|
}
|
|
if (!equals( key.data, (int)key.size, "UIDVALIDITY", 11 ) &&
|
|
(ret = tdb->get( tdb, NULL, &key, &value, 0 ))) {
|
|
if (ret != DB_NOTFOUND) {
|
|
tdb->err( tdb, ret, "Maildir error: tdb->get()" );
|
|
break;
|
|
}
|
|
if ((ret = dbc->c_del( dbc, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->c_del()" );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
dbc->c_close( dbc );
|
|
}
|
|
tdb->close( tdb, 0 );
|
|
}
|
|
#endif /* USE_DB */
|
|
qsort( msglist->array.data, msglist->array.size, sizeof(msg_t), maildir_compare );
|
|
for (uid = i = 0; i < msglist->array.size; i++) {
|
|
entry = &msglist->array.data[i];
|
|
if (entry->uid != UINT_MAX) {
|
|
if (uid == entry->uid) {
|
|
#if 1
|
|
/* See comment in maildir_uidval_lock() why this is fatal. */
|
|
error( "Maildir error: duplicate UID %u in %s.\n", uid, ctx->path );
|
|
maildir_free_scan( msglist );
|
|
return DRV_BOX_BAD;
|
|
#else
|
|
notice( "Maildir notice: duplicate UID in %s; changing UIDVALIDITY.\n", ctx->path );
|
|
if ((ret = maildir_init_uid( ctx )) != DRV_OK) {
|
|
maildir_free_scan( msglist );
|
|
return ret;
|
|
}
|
|
maildir_free_scan( msglist );
|
|
goto again;
|
|
#endif
|
|
}
|
|
uid = entry->uid;
|
|
if (uid > ctx->nuid) {
|
|
/* In principle, we could just warn and top up nuid. However, getting into this
|
|
* situation might indicate some serious trouble, so let's not make it worse. */
|
|
error( "Maildir error: UID %u is beyond highest assigned UID %u in %s.\n",
|
|
uid, ctx->nuid, ctx->path );
|
|
maildir_free_scan( msglist );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
fnl = 0;
|
|
#ifdef USE_DB
|
|
} else if (ctx->usedb) {
|
|
if ((ret = maildir_set_uid( ctx, entry->base, &uid )) != DRV_OK) {
|
|
maildir_free_scan( msglist );
|
|
return ret;
|
|
}
|
|
entry->uid = uid;
|
|
fnl = 0;
|
|
#endif /* USE_DB */
|
|
} else {
|
|
if ((ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK) {
|
|
maildir_free_scan( msglist );
|
|
return ret;
|
|
}
|
|
entry->uid = uid;
|
|
if ((u = strstr( entry->base, ",U=" )))
|
|
for (ru = u + 3; isdigit( (uchar)*ru ); ru++);
|
|
else
|
|
u = ru = strchr( entry->base, conf->info_delimiter );
|
|
fnl = (u ?
|
|
nfsnprintf( buf + bl, _POSIX_PATH_MAX - bl, "%s/%.*s,U=%u%s", subdirs[entry->recent], (int)(u - entry->base), entry->base, uid, ru ) :
|
|
nfsnprintf( buf + bl, _POSIX_PATH_MAX - bl, "%s/%s,U=%u", subdirs[entry->recent], entry->base, uid ))
|
|
- 4;
|
|
memcpy( nbuf, buf, (size_t)(bl + 4) );
|
|
nfsnprintf( nbuf + bl + 4, _POSIX_PATH_MAX - bl - 4, "%s", entry->base );
|
|
if (rename( nbuf, buf )) {
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot rename %s to %s", nbuf, buf );
|
|
fail:
|
|
maildir_free_scan( msglist );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
retry:
|
|
maildir_free_scan( msglist );
|
|
goto again;
|
|
}
|
|
free( entry->base );
|
|
entry->base = nfstrndup( buf + bl + 4, (size_t)fnl );
|
|
}
|
|
int want_size = ((ctx->opts & OPEN_NEW_SIZE) && uid > ctx->newuid);
|
|
int want_tuid = ((ctx->opts & OPEN_FIND) && uid >= ctx->finduid);
|
|
int want_msgid = ((ctx->opts & OPEN_OLD_IDS) && uid <= ctx->pairuid);
|
|
if (!want_size && !want_tuid && !want_msgid)
|
|
continue;
|
|
if (!fnl)
|
|
nfsnprintf( buf + bl, _POSIX_PATH_MAX - bl, "%s/%s", subdirs[entry->recent], entry->base );
|
|
if (want_size) {
|
|
if (stat( buf, &st )) {
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot stat %s", buf );
|
|
goto fail;
|
|
}
|
|
goto retry;
|
|
}
|
|
// The clipped value is good enough for MaxSize comparisons.
|
|
entry->size = st.st_size > UINT_MAX ? UINT_MAX : (uint)st.st_size;
|
|
}
|
|
if (want_tuid || want_msgid) {
|
|
if (!(f = fopen( buf, "r" ))) {
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot open %s", buf );
|
|
goto fail;
|
|
}
|
|
goto retry;
|
|
}
|
|
int off, in_msgid = 0;
|
|
char lnbuf[1000]; // Says RFC2822
|
|
while ((want_tuid || want_msgid) && fgets( lnbuf, sizeof(lnbuf), f )) {
|
|
int bufl = strlen( lnbuf );
|
|
if (bufl && lnbuf[bufl - 1] == '\n')
|
|
--bufl;
|
|
if (bufl && lnbuf[bufl - 1] == '\r')
|
|
--bufl;
|
|
if (!bufl)
|
|
break;
|
|
if (want_tuid && starts_with( lnbuf, bufl, "X-TUID: ", 8 )) {
|
|
if (bufl < 8 + TUIDL) {
|
|
error( "Maildir error: malformed X-TUID header in %s\n", buf );
|
|
continue;
|
|
}
|
|
memcpy( entry->tuid, lnbuf + 8, TUIDL );
|
|
want_tuid = 0;
|
|
in_msgid = 0;
|
|
continue;
|
|
}
|
|
if (want_msgid && starts_with_upper( lnbuf, bufl, "MESSAGE-ID:", 11 )) {
|
|
off = 11;
|
|
} else if (in_msgid) {
|
|
if (!isspace( lnbuf[0] )) {
|
|
in_msgid = 0;
|
|
continue;
|
|
}
|
|
off = 1;
|
|
} else {
|
|
continue;
|
|
}
|
|
while (off < bufl && isspace( lnbuf[off] ))
|
|
off++;
|
|
if (off == bufl) {
|
|
in_msgid = 1;
|
|
continue;
|
|
}
|
|
entry->msgid = nfstrndup( lnbuf + off, (size_t)(bufl - off) );
|
|
want_msgid = 0;
|
|
in_msgid = 0;
|
|
}
|
|
fclose( f );
|
|
}
|
|
}
|
|
ctx->uvok = 1;
|
|
}
|
|
return DRV_OK;
|
|
}
|
|
|
|
static void
|
|
maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry )
|
|
{
|
|
msg->base = entry->base;
|
|
entry->base = NULL; /* prevent deletion */
|
|
msg->msgid = entry->msgid;
|
|
entry->msgid = NULL; /* prevent deletion */
|
|
msg->size = entry->size;
|
|
memcpy( msg->tuid, entry->tuid, TUIDL );
|
|
if (entry->recent)
|
|
msg->status |= M_RECENT;
|
|
if (ctx->opts & OPEN_FLAGS) {
|
|
msg->status |= M_FLAGS;
|
|
msg->flags = maildir_parse_flags( ctx->conf->info_prefix, msg->base );
|
|
} else {
|
|
msg->flags = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
maildir_app_msg( maildir_store_t *ctx, maildir_message_t ***msgapp, msg_t *entry )
|
|
{
|
|
maildir_message_t *msg = nfmalloc( sizeof(*msg) );
|
|
msg->next = **msgapp;
|
|
**msgapp = msg;
|
|
*msgapp = &msg->next;
|
|
msg->uid = entry->uid;
|
|
msg->status = 0;
|
|
msg->srec = NULL;
|
|
maildir_init_msg( ctx, msg, entry );
|
|
}
|
|
|
|
static int
|
|
maildir_select_box( store_t *gctx, const char *name )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_store_conf_t *conf = ctx->conf;
|
|
|
|
maildir_cleanup( gctx );
|
|
ctx->msgs = NULL;
|
|
ctx->excs.data = NULL;
|
|
ctx->uvfd = -1;
|
|
#ifdef USE_DB
|
|
ctx->db = NULL;
|
|
ctx->usedb = NULL;
|
|
#endif /* USE_DB */
|
|
ctx->fresh[0] = ctx->fresh[1] = 0;
|
|
if (starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/')) {
|
|
if (!name[5]) {
|
|
ctx->path = nfstrdup( conf->inbox );
|
|
ctx->is_inbox = 1;
|
|
} else {
|
|
ctx->path = maildir_join_path( conf, 1, name + 5 );
|
|
ctx->is_inbox = 0;
|
|
}
|
|
} else {
|
|
if (!(ctx->path = maildir_join_path( conf, 0, name )))
|
|
return DRV_STORE_BAD;
|
|
ctx->is_inbox = 0;
|
|
}
|
|
return ctx->path ? DRV_OK : DRV_BOX_BAD;
|
|
}
|
|
|
|
static const char *
|
|
maildir_get_box_path( store_t *gctx )
|
|
{
|
|
return ((maildir_store_t *)gctx)->path;
|
|
}
|
|
|
|
static void
|
|
maildir_open_box( store_t *gctx,
|
|
void (*cb)( int sts, uint uidvalidity, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
int ret;
|
|
char uvpath[_POSIX_PATH_MAX];
|
|
|
|
if ((ret = maildir_validate( ctx->path, ctx->is_inbox, ctx )) != DRV_OK)
|
|
goto bail;
|
|
|
|
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", ctx->path );
|
|
#ifndef USE_DB
|
|
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) < 0) {
|
|
sys_error( "Maildir error: cannot write %s", uvpath );
|
|
cb( DRV_BOX_BAD, UIDVAL_BAD, aux );
|
|
return;
|
|
}
|
|
#else
|
|
ctx->usedb = NULL;
|
|
if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) {
|
|
nfsnprintf( uvpath, sizeof(uvpath), "%s/.isyncuidmap.db", ctx->path );
|
|
if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) {
|
|
if (ctx->conf->alt_map) {
|
|
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
|
|
goto dbok;
|
|
} else {
|
|
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", ctx->path );
|
|
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
|
|
goto fnok;
|
|
}
|
|
sys_error( "Maildir error: cannot write %s", uvpath );
|
|
cb( DRV_BOX_BAD, UIDVAL_BAD, aux );
|
|
return;
|
|
} else {
|
|
dbok:
|
|
ctx->usedb = nfstrdup( uvpath );
|
|
}
|
|
}
|
|
fnok:
|
|
#endif /* USE_DB */
|
|
ret = maildir_uidval_lock( ctx );
|
|
|
|
bail:
|
|
cb( ret, ctx->uidvalidity, aux );
|
|
}
|
|
|
|
static uint
|
|
maildir_get_uidnext( store_t *gctx )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
return ctx->nuid;
|
|
}
|
|
|
|
static xint
|
|
maildir_get_supported_flags( store_t *gctx ATTR_UNUSED )
|
|
{
|
|
return 255;
|
|
}
|
|
|
|
static void
|
|
maildir_create_box( store_t *gctx,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
cb( maildir_validate( ctx->path, 1, ctx ), aux );
|
|
}
|
|
|
|
static int
|
|
maildir_confirm_box_empty( store_t *gctx )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
msg_t_array_alloc_t msglist;
|
|
|
|
ctx->excs.size = ctx->minuid = ctx->maxuid = ctx->finduid = 0;
|
|
|
|
if (maildir_scan( ctx, &msglist ) != DRV_OK)
|
|
return DRV_BOX_BAD;
|
|
maildir_free_scan( &msglist );
|
|
return ctx->total_msgs ? DRV_BOX_BAD : DRV_OK;
|
|
}
|
|
|
|
static void
|
|
maildir_delete_box( store_t *gctx,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
int i, bl, ret = DRV_OK;
|
|
struct stat st;
|
|
char buf[_POSIX_PATH_MAX];
|
|
|
|
bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", ctx->path );
|
|
if (stat( buf, &st )) {
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot access mailbox '%s'", ctx->path );
|
|
ret = DRV_BOX_BAD;
|
|
}
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
error( "Maildir error: '%s' is no valid mailbox\n", ctx->path );
|
|
ret = DRV_BOX_BAD;
|
|
} else if ((ret = maildir_clear_tmp( buf, sizeof(buf), bl )) == DRV_OK) {
|
|
nfsnprintf( buf + bl, _POSIX_PATH_MAX - bl, ".uidvalidity" );
|
|
if (unlink( buf ) && errno != ENOENT)
|
|
goto badrm;
|
|
#ifdef USE_DB
|
|
nfsnprintf( buf + bl, _POSIX_PATH_MAX - bl, ".isyncuidmap.db" );
|
|
if (unlink( buf ) && errno != ENOENT)
|
|
goto badrm;
|
|
#endif
|
|
/* We delete cur/ last, as it is the indicator for a present mailbox.
|
|
* That way an interrupted operation can be resumed. */
|
|
for (i = 3; --i >= 0; ) {
|
|
memcpy( buf + bl, subdirs[i], 4 );
|
|
if (rmdir( buf ) && errno != ENOENT) {
|
|
badrm:
|
|
sys_error( "Maildir error: cannot remove '%s'", buf );
|
|
ret = DRV_BOX_BAD;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
cb( ret, aux );
|
|
}
|
|
|
|
static int
|
|
maildir_finish_delete_box( store_t *gctx )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
/* Subfolders are not deleted; the deleted folder is only "stripped of its mailboxness".
|
|
* Consequently, the rmdir may legitimately fail. This behavior follows the IMAP spec. */
|
|
if (rmdir( ctx->path ) && errno != ENOENT && errno != ENOTEMPTY) {
|
|
sys_error( "Maildir warning: cannot remove '%s'", ctx->path );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
return DRV_OK;
|
|
}
|
|
|
|
static uint
|
|
maildir_prepare_load_box( store_t *gctx, uint opts )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
|
|
if (opts & OPEN_SETFLAGS)
|
|
opts |= OPEN_OLD;
|
|
if (opts & OPEN_EXPUNGE)
|
|
opts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS;
|
|
ctx->opts = opts;
|
|
return opts;
|
|
}
|
|
|
|
static void
|
|
maildir_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairuid, uint newuid, uint_array_t excs,
|
|
void (*cb)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_message_t **msgapp;
|
|
msg_t_array_alloc_t msglist;
|
|
uint i;
|
|
|
|
ctx->minuid = minuid;
|
|
ctx->maxuid = maxuid;
|
|
ctx->finduid = finduid;
|
|
ctx->pairuid = pairuid;
|
|
ctx->newuid = newuid;
|
|
ARRAY_SQUEEZE( &excs );
|
|
ctx->excs = excs;
|
|
|
|
if (maildir_scan( ctx, &msglist ) != DRV_OK) {
|
|
cb( DRV_BOX_BAD, NULL, 0, 0, aux );
|
|
return;
|
|
}
|
|
msgapp = &ctx->msgs;
|
|
for (i = 0; i < msglist.array.size; i++)
|
|
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
|
|
maildir_free_scan( &msglist );
|
|
|
|
cb( DRV_OK, &ctx->msgs->gen, ctx->total_msgs, ctx->recent_msgs, aux );
|
|
}
|
|
|
|
static int
|
|
maildir_rescan( maildir_store_t *ctx )
|
|
{
|
|
maildir_message_t **msgapp, *msg;
|
|
msg_t_array_alloc_t msglist;
|
|
uint i;
|
|
|
|
ctx->fresh[0] = ctx->fresh[1] = 0;
|
|
if (maildir_scan( ctx, &msglist ) != DRV_OK)
|
|
return DRV_BOX_BAD;
|
|
debug( "Maildir processing rescan of %s:\n", ctx->path );
|
|
for (msgapp = &ctx->msgs, i = 0; (msg = *msgapp) || i < msglist.array.size; ) {
|
|
if (!msg) {
|
|
#if 0
|
|
debug( " adding new message %u\n", msglist.array.data[i].uid );
|
|
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
|
|
#else
|
|
debug( " ignoring new message %u\n", msglist.array.data[i].uid );
|
|
#endif
|
|
i++;
|
|
} else if (i >= msglist.array.size) {
|
|
debug( " purging deleted message %u\n", msg->uid );
|
|
msg->status = M_DEAD;
|
|
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
|
|
msgapp = &msg->next;
|
|
} else if (msglist.array.data[i].uid < msg->uid) {
|
|
/* this should not happen, actually */
|
|
#if 0
|
|
debug( " adding new message %u\n", msglist.array.data[i].uid );
|
|
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
|
|
#else
|
|
debug( " ignoring new message %u\n", msglist.array.data[i].uid );
|
|
#endif
|
|
i++;
|
|
} else if (msglist.array.data[i].uid > msg->uid) {
|
|
debug( " purging deleted message %u\n", msg->uid );
|
|
msg->status = M_DEAD;
|
|
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
|
|
msgapp = &msg->next;
|
|
} else {
|
|
debug( " updating message %u\n", msg->uid );
|
|
msg->status &= ~(M_FLAGS|M_RECENT);
|
|
free( msg->base );
|
|
free( msg->msgid );
|
|
maildir_init_msg( ctx, msg, msglist.array.data + i );
|
|
i++, msgapp = &msg->next;
|
|
}
|
|
}
|
|
maildir_free_scan( &msglist );
|
|
return DRV_OK;
|
|
}
|
|
|
|
static int ATTR_PRINTFLIKE(3, 0)
|
|
maildir_again( maildir_store_t *ctx, maildir_message_t *msg, const char *err, ... )
|
|
{
|
|
int ret;
|
|
|
|
if (errno != ENOENT) {
|
|
va_list va;
|
|
va_start( va, err );
|
|
vsys_error( err, va );
|
|
va_end( va );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
if ((ret = maildir_rescan( ctx )) != DRV_OK)
|
|
return ret;
|
|
return (msg->status & M_DEAD) ? DRV_MSG_BAD : DRV_OK;
|
|
}
|
|
|
|
static void
|
|
maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, int minimal ATTR_UNUSED,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_message_t *msg = (maildir_message_t *)gmsg;
|
|
int fd, ret;
|
|
struct stat st;
|
|
char buf[_POSIX_PATH_MAX];
|
|
|
|
for (;;) {
|
|
nfsnprintf( buf, sizeof(buf), "%s/%s/%s", ctx->path, subdirs[gmsg->status & M_RECENT], msg->base );
|
|
if ((fd = open( buf, O_RDONLY )) >= 0)
|
|
break;
|
|
if ((ret = maildir_again( ctx, msg, "Cannot open %s", buf )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
}
|
|
fstat( fd, &st );
|
|
if (st.st_size > INT_MAX) {
|
|
error( "Maildir error: %s is too big", buf );
|
|
goto mbad;
|
|
}
|
|
data->len = st.st_size;
|
|
if (data->date == -1)
|
|
data->date = st.st_mtime;
|
|
data->data = nfmalloc( data->len );
|
|
if (read( fd, data->data, data->len ) != data->len) {
|
|
sys_error( "Maildir error: cannot read %s", buf );
|
|
mbad:
|
|
close( fd );
|
|
cb( DRV_MSG_BAD, aux );
|
|
return;
|
|
}
|
|
close( fd );
|
|
if (!(gmsg->status & M_FLAGS))
|
|
data->flags = maildir_parse_flags( ctx->conf->info_prefix, msg->base );
|
|
cb( DRV_OK, aux );
|
|
}
|
|
|
|
static int
|
|
maildir_make_flags( char info_delimiter, uchar flags, char *buf )
|
|
{
|
|
int i, d;
|
|
|
|
buf[0] = info_delimiter;
|
|
buf[1] = '2';
|
|
buf[2] = ',';
|
|
for (d = 3, i = 0; i < (int)as(MsgFlags); i++)
|
|
if (flags & (1 << i))
|
|
buf[d++] = MsgFlags[i];
|
|
buf[d] = 0;
|
|
return d;
|
|
}
|
|
|
|
static void
|
|
maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
|
|
void (*cb)( int sts, uint uid, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
const char *box;
|
|
int ret, fd, bl;
|
|
uint uid;
|
|
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[as(MsgFlags) + 3], base[128];
|
|
|
|
bl = nfsnprintf( base, sizeof(base), "%lld.%d_%d.%s", (long long)time( NULL ), Pid, ++MaildirCount, Hostname );
|
|
if (!to_trash) {
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
if ((ret = maildir_set_uid( ctx, base, &uid )) != DRV_OK) {
|
|
free( data->data );
|
|
cb( ret, 0, aux );
|
|
return;
|
|
}
|
|
} else
|
|
#endif /* USE_DB */
|
|
{
|
|
if ((ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK) {
|
|
free( data->data );
|
|
cb( ret, 0, aux );
|
|
return;
|
|
}
|
|
nfsnprintf( base + bl, (int)sizeof(base) - bl, ",U=%u", uid );
|
|
}
|
|
box = ctx->path;
|
|
} else {
|
|
uid = 0;
|
|
box = ctx->trash;
|
|
}
|
|
|
|
maildir_make_flags( ctx->conf->info_delimiter, data->flags, fbuf );
|
|
nfsnprintf( buf, sizeof(buf), "%s/tmp/%s%s", box, base, fbuf );
|
|
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
|
|
if (errno != ENOENT || !to_trash) {
|
|
sys_error( "Maildir error: cannot create %s", buf );
|
|
free( data->data );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
if ((ret = maildir_validate( box, 1, ctx )) != DRV_OK) {
|
|
free( data->data );
|
|
cb( ret, 0, aux );
|
|
return;
|
|
}
|
|
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
|
|
sys_error( "Maildir error: cannot create %s", buf );
|
|
free( data->data );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
}
|
|
ret = write( fd, data->data, data->len );
|
|
free( data->data );
|
|
if (ret != (int)data->len || (UseFSync && (ret = fsync( fd )))) {
|
|
if (ret < 0)
|
|
sys_error( "Maildir error: cannot write %s", buf );
|
|
else
|
|
error( "Maildir error: cannot write %s. Disk full?\n", buf );
|
|
close( fd );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
if (close( fd ) < 0) {
|
|
/* Quota exceeded may cause this. */
|
|
sys_error( "Maildir error: cannot write %s", buf );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
|
|
if (data->date) {
|
|
/* Set atime and mtime according to INTERNALDATE or mtime of source message */
|
|
struct utimbuf utimebuf;
|
|
utimebuf.actime = utimebuf.modtime = data->date;
|
|
if (utime( buf, &utimebuf ) < 0) {
|
|
sys_error( "Maildir error: cannot set times for %s", buf );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Moving seen messages to cur/ is strictly speaking incorrect, but makes mutt happy. */
|
|
nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%s%s", box, subdirs[!(data->flags & F_SEEN)], base, fbuf );
|
|
if (rename( buf, nbuf )) {
|
|
sys_error( "Maildir error: cannot rename %s to %s", buf, nbuf );
|
|
cb( DRV_BOX_BAD, 0, aux );
|
|
return;
|
|
}
|
|
cb( DRV_OK, uid, aux );
|
|
}
|
|
|
|
static void
|
|
maildir_set_msg_flags( store_t *gctx, message_t *gmsg, uint uid ATTR_UNUSED, int add, int del,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_store_conf_t *conf = ctx->conf;
|
|
maildir_message_t *msg = (maildir_message_t *)gmsg;
|
|
char *s, *p;
|
|
uint i;
|
|
int j, ret, ol, fl, bbl, bl, tl;
|
|
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
|
|
|
|
bbl = nfsnprintf( buf, sizeof(buf), "%s/", ctx->path );
|
|
memcpy( nbuf, ctx->path, (size_t)bbl - 1 );
|
|
memcpy( nbuf + bbl - 1, "/cur/", 5 );
|
|
for (;;) {
|
|
bl = bbl + nfsnprintf( buf + bbl, _POSIX_PATH_MAX - bbl, "%s/", subdirs[gmsg->status & M_RECENT] );
|
|
ol = strlen( msg->base );
|
|
if (_POSIX_PATH_MAX - bl < ol + 3 + (int)as(MsgFlags))
|
|
oob();
|
|
memcpy( buf + bl, msg->base, (size_t)ol + 1 );
|
|
memcpy( nbuf + bl, msg->base, (size_t)ol + 1 );
|
|
if ((s = strstr( nbuf + bl, conf->info_prefix ))) {
|
|
s += 3;
|
|
fl = ol - (s - (nbuf + bl));
|
|
for (i = 0; i < as(MsgFlags); i++) {
|
|
if ((p = strchr( s, MsgFlags[i] ))) {
|
|
if (del & (1 << i)) {
|
|
memmove( p, p + 1, (size_t)fl - (size_t)(p - s) );
|
|
fl--;
|
|
}
|
|
} else if (add & (1 << i)) {
|
|
for (j = 0; j < fl && MsgFlags[i] > s[j]; j++);
|
|
fl++;
|
|
memmove( s + j + 1, s + j, (size_t)(fl - j) );
|
|
s[j] = MsgFlags[i];
|
|
}
|
|
}
|
|
tl = ol + 3 + fl;
|
|
} else {
|
|
tl = ol + maildir_make_flags( conf->info_delimiter, (uchar)add, nbuf + bl + ol );
|
|
}
|
|
if (!rename( buf, nbuf ))
|
|
break;
|
|
if ((ret = maildir_again( ctx, msg, "Maildir error: cannot rename %s to %s", buf, nbuf )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
}
|
|
free( msg->base );
|
|
msg->base = nfstrndup( nbuf + bl, (size_t)tl );
|
|
msg->flags |= add;
|
|
msg->flags &= ~del;
|
|
gmsg->status &= ~M_RECENT;
|
|
|
|
cb( DRV_OK, aux );
|
|
}
|
|
|
|
#ifdef USE_DB
|
|
static int
|
|
maildir_purge_msg( maildir_store_t *ctx, const char *name )
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = maildir_uidval_lock( ctx )) != DRV_OK)
|
|
return ret;
|
|
make_key( ctx->conf->info_stop, &key, name );
|
|
if ((ret = ctx->db->del( ctx->db, NULL, &key, 0 ))) {
|
|
ctx->db->err( ctx->db, ret, "Maildir error: db->del()" );
|
|
return DRV_BOX_BAD;
|
|
}
|
|
return DRV_OK;
|
|
}
|
|
#endif /* USE_DB */
|
|
|
|
static void
|
|
maildir_trash_msg( store_t *gctx, message_t *gmsg,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_message_t *msg = (maildir_message_t *)gmsg;
|
|
char *s;
|
|
int ret;
|
|
struct stat st;
|
|
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
|
|
|
|
for (;;) {
|
|
nfsnprintf( buf, sizeof(buf), "%s/%s/%s", ctx->path, subdirs[gmsg->status & M_RECENT], msg->base );
|
|
s = strstr( msg->base, ctx->conf->info_prefix );
|
|
nfsnprintf( nbuf, sizeof(nbuf), "%s/%s/%lld.%d_%d.%s%s", ctx->trash,
|
|
subdirs[gmsg->status & M_RECENT], (long long)time( NULL ), Pid, ++MaildirCount, Hostname, s ? s : "" );
|
|
if (!rename( buf, nbuf ))
|
|
break;
|
|
if (!stat( buf, &st )) {
|
|
if ((ret = maildir_validate( ctx->trash, 1, ctx )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
if (!rename( buf, nbuf ))
|
|
break;
|
|
if (errno != ENOENT) {
|
|
sys_error( "Maildir error: cannot move %s to %s", buf, nbuf );
|
|
cb( DRV_BOX_BAD, aux );
|
|
return;
|
|
}
|
|
}
|
|
if ((ret = maildir_again( ctx, msg, "Maildir error: cannot move %s to %s", buf, nbuf )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
}
|
|
gmsg->status |= M_DEAD;
|
|
ctx->total_msgs--;
|
|
ctx->expunge_callback( gmsg, ctx->callback_aux );
|
|
|
|
#ifdef USE_DB
|
|
if (ctx->usedb) {
|
|
cb( maildir_purge_msg( ctx, msg->base ), aux );
|
|
return;
|
|
}
|
|
#endif /* USE_DB */
|
|
cb( DRV_OK, aux );
|
|
}
|
|
|
|
static void
|
|
maildir_close_box( store_t *gctx,
|
|
void (*cb)( int sts, void *aux ), void *aux )
|
|
{
|
|
maildir_store_t *ctx = (maildir_store_t *)gctx;
|
|
maildir_message_t *msg;
|
|
int basel, retry, ret;
|
|
char buf[_POSIX_PATH_MAX];
|
|
|
|
for (;;) {
|
|
retry = 0;
|
|
basel = nfsnprintf( buf, sizeof(buf), "%s/", ctx->path );
|
|
for (msg = ctx->msgs; msg; msg = msg->next) {
|
|
if (!(msg->status & M_DEAD) && (msg->flags & F_DELETED)) {
|
|
nfsnprintf( buf + basel, _POSIX_PATH_MAX - basel, "%s/%s", subdirs[msg->status & M_RECENT], msg->base );
|
|
if (unlink( buf )) {
|
|
if (errno == ENOENT)
|
|
retry = 1;
|
|
else
|
|
sys_error( "Maildir error: cannot remove %s", buf );
|
|
} else {
|
|
msg->status |= M_DEAD;
|
|
ctx->total_msgs--;
|
|
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
|
|
#ifdef USE_DB
|
|
if (ctx->db && (ret = maildir_purge_msg( ctx, msg->base )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
#endif /* USE_DB */
|
|
}
|
|
}
|
|
}
|
|
if (!retry) {
|
|
cb( DRV_OK, aux );
|
|
return;
|
|
}
|
|
if ((ret = maildir_rescan( ctx )) != DRV_OK) {
|
|
cb( ret, aux );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
maildir_cancel_cmds( store_t *gctx ATTR_UNUSED,
|
|
void (*cb)( void *aux ), void *aux )
|
|
{
|
|
cb( aux );
|
|
}
|
|
|
|
static void
|
|
maildir_commit_cmds( store_t *gctx )
|
|
{
|
|
(void) gctx;
|
|
}
|
|
|
|
static uint
|
|
maildir_get_memory_usage( store_t *gctx ATTR_UNUSED )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
maildir_get_fail_state( store_conf_t *gconf )
|
|
{
|
|
return ((maildir_store_conf_t *)gconf)->failed;
|
|
}
|
|
|
|
static int
|
|
maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
|
|
{
|
|
maildir_store_conf_t *store;
|
|
|
|
if (strcasecmp( "MaildirStore", cfg->cmd ))
|
|
return 0;
|
|
store = nfzalloc( sizeof(*store) );
|
|
store->info_delimiter = FieldDelimiter;
|
|
store->driver = &maildir_driver;
|
|
store->name = nfstrdup( cfg->val );
|
|
|
|
while (getcline( cfg ) && cfg->cmd) {
|
|
if (!strcasecmp( "Inbox", cfg->cmd )) {
|
|
store->inbox = expand_strdup( cfg->val, cfg );
|
|
} else if (!strcasecmp( "Path", cfg->cmd )) {
|
|
store->path = expand_strdup( cfg->val, cfg );
|
|
#ifdef USE_DB
|
|
} else if (!strcasecmp( "AltMap", cfg->cmd )) {
|
|
store->alt_map = parse_bool( cfg );
|
|
#endif /* USE_DB */
|
|
} else if (!strcasecmp( "InfoDelimiter", cfg->cmd )) {
|
|
if (strlen( cfg->val ) != 1) {
|
|
error( "%s:%d: Info delimiter must be exactly one character long\n", cfg->file, cfg->line );
|
|
cfg->err = 1;
|
|
continue;
|
|
}
|
|
store->info_delimiter = cfg->val[0];
|
|
if (!ispunct( store->info_delimiter )) {
|
|
error( "%s:%d: Info delimiter must be a punctuation character\n", cfg->file, cfg->line );
|
|
cfg->err = 1;
|
|
continue;
|
|
}
|
|
} else if (!strcasecmp( "SubFolders", cfg->cmd )) {
|
|
if (!strcasecmp( "Verbatim", cfg->val )) {
|
|
store->sub_style = SUB_VERBATIM;
|
|
} else if (!strcasecmp( "Maildir++", cfg->val )) {
|
|
store->sub_style = SUB_MAILDIRPP;
|
|
} else if (!strcasecmp( "Legacy", cfg->val )) {
|
|
store->sub_style = SUB_LEGACY;
|
|
} else {
|
|
error( "%s:%d: Unrecognized SubFolders style\n", cfg->file, cfg->line );
|
|
cfg->err = 1;
|
|
}
|
|
} else {
|
|
parse_generic_store( &store->gen, cfg, "MaildirStore" );
|
|
}
|
|
}
|
|
if (!store->inbox)
|
|
store->inbox = expand_strdup( "~/Maildir", NULL );
|
|
if (store->sub_style == SUB_MAILDIRPP && store->path) {
|
|
error( "Maildir store '%s': Setting Path is incompatible with 'SubFolders Maildir++'\n", store->name );
|
|
cfg->err = 1;
|
|
}
|
|
nfasprintf( &store->info_prefix, "%c2,", store->info_delimiter );
|
|
nfasprintf( &store->info_stop, "%c,", store->info_delimiter );
|
|
*storep = &store->gen;
|
|
return 1;
|
|
}
|
|
|
|
static uint
|
|
maildir_get_caps( store_t *gctx ATTR_UNUSED )
|
|
{
|
|
return 0; /* XXX DRV_CRLF? */
|
|
}
|
|
|
|
struct driver maildir_driver = {
|
|
maildir_get_caps,
|
|
maildir_parse_store,
|
|
maildir_cleanup_drv,
|
|
maildir_alloc_store,
|
|
maildir_set_callbacks,
|
|
maildir_connect_store,
|
|
maildir_free_store,
|
|
maildir_free_store, /* _cancel_, but it's the same */
|
|
maildir_list_store,
|
|
maildir_select_box,
|
|
maildir_get_box_path,
|
|
maildir_create_box,
|
|
maildir_open_box,
|
|
maildir_get_uidnext,
|
|
maildir_get_supported_flags,
|
|
maildir_confirm_box_empty,
|
|
maildir_delete_box,
|
|
maildir_finish_delete_box,
|
|
maildir_prepare_load_box,
|
|
maildir_load_box,
|
|
maildir_fetch_msg,
|
|
maildir_store_msg,
|
|
NULL, // find_new_msgs
|
|
maildir_set_msg_flags,
|
|
maildir_trash_msg,
|
|
maildir_close_box,
|
|
maildir_cancel_cmds,
|
|
maildir_commit_cmds,
|
|
maildir_get_memory_usage,
|
|
maildir_get_fail_state,
|
|
};
|