/*
* Heirloom mailx - a mail user agent derived from Berkeley Mail.
*
* Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
*/
/*
* Copyright (c) 2004
* Gunnar Ritter. All rights reserved.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Gunnar Ritter
* and his contributors.
* 4. Neither the name of Gunnar Ritter nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``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 GUNNAR RITTER OR CONTRIBUTORS 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.
*/
#ifndef lint
#ifdef DOSCCS
static char sccsid[] = "@(#)maildir.c 1.20 (gritter) 12/28/06";
#endif
#endif /* not lint */
#include "config.h"
#include "rcv.h"
#include "extern.h"
#include <time.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
/*
* Mail -- a mail program
*
* Maildir folder support.
*/
static struct mditem {
struct message *md_data;
unsigned md_hash;
} *mdtable;
static long mdprime;
static sigjmp_buf maildirjmp;
static int maildir_setfile1(const char *name, int newmail, int omsgCount);
static int mdcmp(const void *a, const void *b);
static int subdir(const char *name, const char *sub, int newmail);
static void cleantmp(const char *name);
static void append(const char *name, const char *sub, const char *fn);
static void readin(const char *name, struct message *m);
static void maildir_update(void);
static void move(struct message *m);
static char *mkname(time_t t, enum mflag f, const char *pref);
static void maildircatch(int s);
static enum okay maildir_append1(const char *name, FILE *fp, off_t off1,
long size, enum mflag flag);
static enum okay trycreate(const char *name);
static enum okay mkmaildir(const char *name);
static struct message *mdlook(const char *name, struct message *data);
static void mktable(void);
static enum okay subdir_remove(const char *name, const char *sub);
int
maildir_setfile(const char *name, int newmail, int isedit)
{
sighandler_type saveint;
struct cw cw;
int i = -1, omsgCount;
(void)&saveint;
(void)&i;
omsgCount = msgCount;
if (cwget(&cw) == STOP) {
fprintf(stderr, "Fatal: Cannot open current directory\n");
return -1;
}
if (!newmail)
quit();
saveint = safe_signal(SIGINT, SIG_IGN);
if (chdir(name) < 0) {
fprintf(stderr, "Cannot change directory to \"%s\".\n", name);
cwrelse(&cw);
return -1;
}
if (!newmail) {
edit = isedit;
if (mb.mb_itf) {
fclose(mb.mb_itf);
mb.mb_itf = NULL;
}
if (mb.mb_otf) {
fclose(mb.mb_otf);
mb.mb_otf = NULL;
}
initbox(name);
mb.mb_type = MB_MAILDIR;
}
mdtable = NULL;
if (sigsetjmp(maildirjmp, 1) == 0) {
if (newmail)
mktable();
if (saveint != SIG_IGN)
safe_signal(SIGINT, maildircatch);
i = maildir_setfile1(name, newmail, omsgCount);
}
if (newmail)
free(mdtable);
safe_signal(SIGINT, saveint);
if (i < 0) {
mb.mb_type = MB_VOID;
*mailname = '\0';
msgCount = 0;
}
if (cwret(&cw) == STOP) {
fputs("Fatal: Cannot change back to current directory.\n",
stderr);
abort();
}
cwrelse(&cw);
setmsize(msgCount);
if (newmail && mb.mb_sorted && msgCount > omsgCount) {
mb.mb_threaded = 0;
sort((void *)-1);
}
if (!newmail)
sawcom = 0;
if (!newmail && !edit && msgCount == 0) {
if (mb.mb_type == MB_MAILDIR && value("emptystart") == NULL)
fprintf(stderr, "No mail at %s\n", name);
return 1;
}
if (newmail && msgCount > omsgCount)
newmailinfo(omsgCount);
return 0;
}
static int
maildir_setfile1(const char *name, int newmail, int omsgCount)
{
int i;
if (!newmail)
cleantmp(name);
mb.mb_perm = Rflag ? 0 : MB_DELE;
if ((i = subdir(name, "cur", newmail)) != 0)
return i;
if ((i = subdir(name, "new", newmail)) != 0)
return i;
append(name, NULL, NULL);
for (i = newmail?omsgCount:0; i < msgCount; i++)
readin(name, &message[i]);
if (newmail) {
if (msgCount > omsgCount)
qsort(&message[omsgCount],
msgCount - omsgCount,
sizeof *message, mdcmp);
} else {
if (msgCount)
qsort(message, msgCount, sizeof *message, mdcmp);
}
return msgCount;
}
/*
* In combination with the names from mkname(), this comparison function
* ensures that the order of messages in a maildir folder created by mailx
* remains always the same. In effect, if a mbox folder is transferred to
* a maildir folder by 'copy *', the order of the messages in mailx will
* not change.
*/
static int
mdcmp(const void *a, const void *b)
{
long i;
if ((i = ((struct message *)a)->m_time -
((struct message *)b)->m_time) == 0)
i = strcmp(&((struct message *)a)->m_maildir_file[4],
&((struct message *)b)->m_maildir_file[4]);
return i;
}
static int
subdir(const char *name, const char *sub, int newmail)
{
DIR *dirfd;
struct dirent *dp;
if ((dirfd = opendir(sub)) == NULL) {
fprintf(stderr, "Cannot open directory \"%s/%s\".\n",
name, sub);
return -1;
}
if (access(sub, W_OK) < 0)
mb.mb_perm = 0;
while ((dp = readdir(dirfd)) != NULL) {
if (dp->d_name[0] == '.' &&
(dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' &&
dp->d_name[2] == '\0')))
continue;
if (dp->d_name[0] == '.')
continue;
if (!newmail || mdlook(dp->d_name, NULL) == NULL)
append(name, sub, dp->d_name);
}
closedir(dirfd);
return 0;
}
static void
cleantmp(const char *name)
{
struct stat st;
DIR *dirfd;
struct dirent *dp;
char *fn = NULL;
size_t fnsz = 0, ssz;
time_t now;
if ((dirfd = opendir("tmp")) == NULL)
return;
time(&now);
while ((dp = readdir(dirfd)) != NULL) {
if (dp->d_name[0] == '.' &&
(dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' &&
dp->d_name[2] == '\0')))
continue;
if (dp->d_name[0] == '.')
continue;
if ((ssz = strlen(dp->d_name)) + 5 > fnsz) {
free(fn);
fn = smalloc(fnsz = ssz + 40);
}
strcpy(fn, "tmp/");
strcpy(&fn[4], dp->d_name);
if (stat(fn, &st) < 0)
continue;
if (st.st_atime + 36*3600 < now)
unlink(fn);
}
free(fn);
closedir(dirfd);
}
static void
append(const char *name, const char *sub, const char *fn)
{
struct message *m;
size_t sz;
time_t t = 0;
enum mflag f = MUSED|MNOFROM|MNEWEST;
const char *cp;
char *xp;
if (fn && sub) {
if (strcmp(sub, "new") == 0)
f |= MNEW;
t = strtol(fn, &xp, 10);
if ((cp = strrchr(xp, ',')) != NULL &&
cp > &xp[2] && cp[-1] == '2' && cp[-2] == ':') {
while (*++cp) {
switch (*cp) {
case 'F':
f |= MFLAGGED;
break;
case 'R':
f |= MANSWERED;
break;
case 'S':
f |= MREAD;
break;
case 'T':
f |= MDELETED;
break;
case 'D':
f |= MDRAFT;
break;
}
}
}
}
if (msgCount + 1 >= msgspace) {
const int chunk = 64;
message = srealloc(message,
(msgspace += chunk) * sizeof *message);
memset(&message[msgCount], 0, chunk * sizeof *message);
}
if (fn == NULL || sub == NULL)
return;
m = &message[msgCount++];
m->m_maildir_file = smalloc((sz = strlen(sub)) + strlen(fn) + 2);
strcpy(m->m_maildir_file, sub);
m->m_maildir_file[sz] = '/';
strcpy(&m->m_maildir_file[sz+1], fn);
m->m_time = t;
m->m_flag = f;
m->m_maildir_hash = ~pjw(fn);
return;
}
static void
readin(const char *name, struct message *m)
{
char *buf, *bp;
size_t bufsize, buflen, count;
long size = 0, lines = 0;
off_t offset;
FILE *fp;
int emptyline = 0;
if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
fprintf(stderr, "Cannot read \"%s/%s\" for message %d\n",
name, m->m_maildir_file, m - &message[0] + 1);
m->m_flag |= MHIDDEN;
return;
}
buf = smalloc(bufsize = LINESIZE);
buflen = 0;
count = fsize(fp);
fseek(mb.mb_otf, 0L, SEEK_END);
offset = ftell(mb.mb_otf);
while (fgetline(&buf, &bufsize, &count, &buflen, fp, 1) != NULL) {
bp = buf;
if (buf[0] == 'F' && buf[1] == 'r' && buf[2] == 'o' &&
buf[3] == 'm' && buf[4] == ' ') {
putc('>', mb.mb_otf);
size++;
}
lines++;
size += fwrite(bp, 1, buflen, mb.mb_otf);
emptyline = *bp == '\n';
}
if (!emptyline) {
putc('\n', mb.mb_otf);
lines++;
size++;
}
Fclose(fp);
fflush(mb.mb_otf);
m->m_size = m->m_xsize = size;
m->m_lines = m->m_xlines = lines;
m->m_block = mailx_blockof(offset);
m->m_offset = mailx_offsetof(offset);
free(buf);
substdate(m);
}
void
maildir_quit(void)
{
sighandler_type saveint;
struct cw cw;
(void)&saveint;
if (cwget(&cw) == STOP) {
fprintf(stderr, "Fatal: Cannot open current directory\n");
return;
}
saveint = safe_signal(SIGINT, SIG_IGN);
if (chdir(mailname) < 0) {
fprintf(stderr, "Cannot change directory to \"%s\".\n",
mailname);
cwrelse(&cw);
return;
}
if (sigsetjmp(maildirjmp, 1) == 0) {
if (saveint != SIG_IGN)
safe_signal(SIGINT, maildircatch);
maildir_update();
}
safe_signal(SIGINT, saveint);
if (cwret(&cw) == STOP) {
fputs("Fatal: Cannot change back to current directory.\n",
stderr);
abort();
}
cwrelse(&cw);
}
static void
maildir_update(void)
{
FILE *readstat = NULL;
struct message *m;
int dodel, c, gotcha = 0, held = 0, modflags = 0;
if (mb.mb_perm == 0)
goto free;
if (Tflag != NULL) {
if ((readstat = Zopen(Tflag, "w", NULL)) == NULL)
Tflag = NULL;
}
if (!edit) {
holdbits();
for (m = &message[0], c = 0; m < &message[msgCount]; m++) {
if (m->m_flag & MBOX)
c++;
}
if (c > 0)
if (makembox() == STOP)
goto bypass;
}
for (m = &message[0], gotcha=0, held=0; m < &message[msgCount]; m++) {
if (readstat != NULL && (m->m_flag & (MREAD|MDELETED)) != 0) {
char *id;
if ((id = hfield("message-id", m)) != NULL ||
(id = hfield("article-id", m)) != NULL)
fprintf(readstat, "%s\n", id);
}
if (edit)
dodel = m->m_flag & MDELETED;
else
dodel = !((m->m_flag&MPRESERVE) ||
(m->m_flag&MTOUCH) == 0);
if (dodel) {
if (unlink(m->m_maildir_file) < 0)
fprintf(stderr, "Cannot delete file \"%s/%s\" "
"for message %d.\n",
mailname, m->m_maildir_file,
m - &message[0] + 1);
else
gotcha++;
} else {
if ((m->m_flag&(MREAD|MSTATUS)) == (MREAD|MSTATUS) ||
m->m_flag & (MNEW|MBOXED|MSAVED|MSTATUS|
MFLAG|MUNFLAG|
MANSWER|MUNANSWER|
MDRAFT|MUNDRAFT)) {
move(m);
modflags++;
}
held++;
}
}
bypass: if (readstat != NULL)
Fclose(readstat);
if ((gotcha || modflags) && edit) {
printf(catgets(catd, CATSET, 168, "\"%s\" "), mailname);
printf(value("bsdcompat") || value("bsdmsgs") ?
catgets(catd, CATSET, 170, "complete\n") :
catgets(catd, CATSET, 212, "updated.\n"));
} else if (held && !edit && mb.mb_perm != 0) {
if (held == 1)
printf(catgets(catd, CATSET, 155,
"Held 1 message in %s\n"), mailname);
else if (held > 1)
printf(catgets(catd, CATSET, 156,
"Held %d messages in %s\n"), held, mailname);
}
fflush(stdout);
free: for (m = &message[0]; m < &message[msgCount]; m++)
free(m->m_maildir_file);
}
static void
move(struct message *m)
{
char *fn, *new;
fn = mkname(0, m->m_flag, &m->m_maildir_file[4]);
new = savecat("cur/", fn);
if (strcmp(m->m_maildir_file, new) == 0)
return;
if (link(m->m_maildir_file, new) < 0) {
fprintf(stderr, "Cannot link \"%s/%s\" to \"%s/%s\": "
"message %d not touched.\n",
mailname, m->m_maildir_file,
mailname, new,
m - &message[0] + 1);
return;
}
if (unlink(m->m_maildir_file) < 0)
fprintf(stderr, "Cannot unlink \"%s/%s\".\n",
mailname, m->m_maildir_file);
}
static char *
mkname(time_t t, enum mflag f, const char *pref)
{
static unsigned long count;
static pid_t mypid;
char *cp;
static char *node;
int size, n, i;
if (pref == NULL) {
if (mypid == 0)
mypid = getpid();
if (node == NULL) {
cp = nodename(0);
n = size = 0;
do {
if (n < size + 8)
node = srealloc(node, size += 20);
switch (*cp) {
case '/':
node[n++] = '\\', node[n++] = '0',
node[n++] = '5', node[n++] = '7';
break;
case ':':
node[n++] = '\\', node[n++] = '0',
node[n++] = '7', node[n++] = '2';
break;
default:
node[n++] = *cp;
}
} while (*cp++);
}
size = 60 + strlen(node);
cp = salloc(size);
n = snprintf(cp, size, "%lu.%06lu_%06lu.%s:2,",
(unsigned long)t,
(unsigned long)mypid, ++count, node);
} else {
size = (n = strlen(pref)) + 13;
cp = salloc(size);
strcpy(cp, pref);
for (i = n; i > 3; i--)
if (cp[i-1] == ',' && cp[i-2] == '2' &&
cp[i-3] == ':') {
n = i;
break;
}
if (i <= 3) {
strcpy(&cp[n], ":2,");
n += 3;
}
}
if (n < size - 7) {
if (f & MDRAFTED)
cp[n++] = 'D';
if (f & MFLAGGED)
cp[n++] = 'F';
if (f & MANSWERED)
cp[n++] = 'R';
if (f & MREAD)
cp[n++] = 'S';
if (f & MDELETED)
cp[n++] = 'T';
cp[n] = '\0';
}
return cp;
}
static void
maildircatch(int s)
{
siglongjmp(maildirjmp, s);
}
enum okay
maildir_append(const char *name, FILE *fp)
{
char *buf, *bp, *lp;
size_t bufsize, buflen, count;
off_t off1 = -1, offs;
int inhead = 1;
int flag = MNEW|MNEWEST;
long size = 0;
enum okay ok;
if (mkmaildir(name) != OKAY)
return STOP;
buf = smalloc(bufsize = LINESIZE);
buflen = 0;
count = fsize(fp);
offs = ftell(fp);
do {
bp = fgetline(&buf, &bufsize, &count, &buflen, fp, 1);
if (bp == NULL || strncmp(buf, "From ", 5) == 0) {
if (off1 != (off_t)-1) {
ok = maildir_append1(name, fp, off1,
size, flag);
if (ok == STOP)
return STOP;
fseek(fp, offs+buflen, SEEK_SET);
}
off1 = offs + buflen;
size = 0;
inhead = 1;
flag = MNEW;
} else
size += buflen;
offs += buflen;
if (bp && buf[0] == '\n')
inhead = 0;
else if (bp && inhead && ascncasecmp(buf, "status", 6) == 0) {
lp = &buf[6];
while (whitechar(*lp&0377))
lp++;
if (*lp == ':')
while (*++lp != '\0')
switch (*lp) {
case 'R':
flag |= MREAD;
break;
case 'O':
flag &= ~MNEW;
break;
}
} else if (bp && inhead &&
ascncasecmp(buf, "x-status", 8) == 0) {
lp = &buf[8];
while (whitechar(*lp&0377))
lp++;
if (*lp == ':')
while (*++lp != '\0')
switch (*lp) {
case 'F':
flag |= MFLAGGED;
break;
case 'A':
flag |= MANSWERED;
break;
case 'T':
flag |= MDRAFTED;
break;
}
}
} while (bp != NULL);
free(buf);
return OKAY;
}
static enum okay
maildir_append1(const char *name, FILE *fp, off_t off1, long size,
enum mflag flag)
{
const int attempts = 43200;
struct stat st;
char buf[4096];
char *fn, *tmp, *new;
FILE *op;
long n, z;
int i;
time_t now;
for (i = 0; i < attempts; i++) {
time(&now);
fn = mkname(now, flag, NULL);
tmp = salloc(n = strlen(name) + strlen(fn) + 6);
snprintf(tmp, n, "%s/tmp/%s", name, fn);
if (stat(tmp, &st) < 0 && errno == ENOENT)
break;
sleep(2);
}
if (i >= attempts) {
fprintf(stderr,
"Cannot create unique file name in \"%s/tmp\".\n",
name);
return STOP;
}
if ((op = Fopen(tmp, "w")) == NULL) {
fprintf(stderr, "Cannot write to \"%s\".\n", tmp);
return STOP;
}
fseek(fp, off1, SEEK_SET);
while (size > 0) {
z = size > sizeof buf ? sizeof buf : size;
if ((n = fread(buf, 1, z, fp)) != z ||
fwrite(buf, 1, n, op) != n) {
fprintf(stderr, "Error writing to \"%s\".\n", tmp);
Fclose(op);
unlink(tmp);
return STOP;
}
size -= n;
}
Fclose(op);
new = salloc(n = strlen(name) + strlen(fn) + 6);
snprintf(new, n, "%s/new/%s", name, fn);
if (link(tmp, new) < 0) {
fprintf(stderr, "Cannot link \"%s\" to \"%s\".\n", tmp, new);
return STOP;
}
if (unlink(tmp) < 0)
fprintf(stderr, "Cannot unlink \"%s\".\n", tmp);
return OKAY;
}
static enum okay
trycreate(const char *name)
{
struct stat st;
if (stat(name, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "\"%s\" is not a directory.\n", name);
return STOP;
}
} else if (makedir(name) != OKAY) {
fprintf(stderr, "Cannot create directory \"%s\".\n", name);
return STOP;
} else
imap_created_mailbox++;
return OKAY;
}
static enum okay
mkmaildir(const char *name)
{
char *np;
size_t sz;
enum okay ok = STOP;
if (trycreate(name) == OKAY) {
np = ac_alloc((sz = strlen(name)) + 5);
strcpy(np, name);
strcpy(&np[sz], "/tmp");
if (trycreate(np) == OKAY) {
strcpy(&np[sz], "/new");
if (trycreate(np) == OKAY) {
strcpy(&np[sz], "/cur");
if (trycreate(np) == OKAY)
ok = OKAY;
}
}
ac_free(np);
}
return ok;
}
static struct message *
mdlook(const char *name, struct message *data)
{
struct mditem *md;
unsigned c, h, n = 0;
if (data && data->m_maildir_hash)
h = ~data->m_maildir_hash;
else
h = pjw(name);
h %= mdprime;
md = &mdtable[c = h];
while (md->md_data != NULL) {
if (strcmp(&md->md_data->m_maildir_file[4], name) == 0)
break;
c += n&1 ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
n++;
while (c >= mdprime)
c -= mdprime;
md = &mdtable[c];
}
if (data != NULL && md->md_data == NULL)
md->md_data = data;
return md->md_data ? md->md_data : NULL;
}
static void
mktable(void)
{
int i;
mdprime = nextprime(msgCount);
mdtable = scalloc(mdprime, sizeof *mdtable);
for (i = 0; i < msgCount; i++)
mdlook(&message[i].m_maildir_file[4], &message[i]);
}
static enum okay
subdir_remove(const char *name, const char *sub)
{
char *path;
int pathsize, pathend, namelen, sublen, n;
DIR *dirfd;
struct dirent *dp;
namelen = strlen(name);
sublen = strlen(sub);
path = smalloc(pathsize = namelen + sublen + 30);
strcpy(path, name);
path[namelen] = '/';
strcpy(&path[namelen+1], sub);
path[namelen+sublen+1] = '/';
path[pathend = namelen + sublen + 2] = '\0';
if ((dirfd = opendir(path)) == NULL) {
perror(path);
free(path);
return STOP;
}
while ((dp = readdir(dirfd)) != NULL) {
if (dp->d_name[0] == '.' &&
(dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' &&
dp->d_name[2] == '\0')))
continue;
if (dp->d_name[0] == '.')
continue;
n = strlen(dp->d_name);
if (pathend + n + 1 > pathsize)
path = srealloc(path, pathsize = pathend + n + 30);
strcpy(&path[pathend], dp->d_name);
if (unlink(path) < 0) {
perror(path);
closedir(dirfd);
free(path);
return STOP;
}
}
closedir(dirfd);
path[pathend] = '\0';
if (rmdir(path) < 0) {
perror(path);
free(path);
return STOP;
}
free(path);
return OKAY;
}
enum okay
maildir_remove(const char *name)
{
if (subdir_remove(name, "tmp") == STOP ||
subdir_remove(name, "new") == STOP ||
subdir_remove(name, "cur") == STOP)
return STOP;
if (rmdir(name) < 0) {
perror(name);
return STOP;
}
return OKAY;
}
syntax highlighted by Code2HTML, v. 0.9.1