/*
* Copyright (c) 1999 Ian Freislich
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR 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.
*
* $Id: mbox_mailidx.c,v 1.22 2003/01/24 09:35:19 ianf Exp $
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <netinet/in.h>
#include <sys/mman.h>
#ifdef SOLARIS
#include "/usr/ucbinclude/fcntl.h"
#endif
#include <ctype.h>
#include <db.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <md5.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <time.h>
#include <sys/uio.h>
#include <unistd.h>
#include "poputil.h"
#include "private.h"
/* Need this global variable to get configuration parameters to the compar()
* routine
*/
static int flags;
struct mbox_idx
{
int count;
int bytes;
int remove;
int expire;
int *idx_num; /* The number of the index of this message */
int *idx_flags; /* a copy of the flags for this message */
};
static int compar(const void *, const void *);
static int mailidx_get_message(struct mail_idx *, struct mbox_idx *, int);
static int mailidx_get_message_top(struct mail_idx *, struct mbox_idx *,
int, int);
static int mailidx_get_uidl_all(struct mail_idx *, struct mbox_idx *);
static int mailidx_list_all(struct mail_idx *, struct mbox_idx *);
static int mailidx_is_message(struct mbox_idx *, int);
static int mailidx_open(struct mail_idx *, struct mbox_idx *, char *);
static void mailidx_close(struct mail_idx *, struct mbox_idx *, char *);
/* Perform open/extend/close opperations and the various locking options
* at open time on the users mailbox. When the mailbox is closed, truncate
* the index file to MAXINCR if the free space at the end of the index is
* greater than MAXINCR.
*/
int
mailidx_ctl(struct mail_idx *idx, char *path, int cmd, ...)
{
va_list ap;
int flags = 0, result, finalsize;
static char buffer[MAXBUFLEN + 1];
static int fd = -1;
static int len = 0;
struct stat stat_s;
struct flock fl;
fl.l_start = 0;
fl.l_len = 0;
fl.l_pid = getpid();
fl.l_whence = SEEK_SET;
switch (cmd) {
case IDX_OPEN:
if (fd > -1) /*Don't reopen the file*/
return(FALSE);
snprintf(buffer, MAXBUFLEN, "%s.idx", path);
fd = open(buffer, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
if (fd <0)
return(FALSE);
va_start(ap, cmd);
flags = va_arg(ap, int);
va_end(ap);
fl.l_type = 0;
fl.l_type = (flags & IDX_O_READ) ? F_RDLCK : fl.l_type;
fl.l_type = (flags & IDX_O_MODIFY) ? F_WRLCK : fl.l_type;
fl.l_type = (flags & IDX_O_APPEND) ? F_RDLCK : fl.l_type;
result = fcntl(fd, (flags & IDX_NONBLOCK) ? F_SETLK : F_SETLKW,
&fl);
if (result < 0) {
close(fd);
fd = -1;
return(FALSE);
}
if (stat(buffer, &stat_s) < 0) {
close(fd);
fd = -1;
return(FALSE);
}
len = stat_s.st_size;
idx->hdr = mmap((void *)NULL, len, PROT_WRITE|PROT_READ,
MAP_SHARED, fd, 0);
if (idx->hdr == MAP_FAILED) {
close(fd);
fd = -1;
return(FALSE);
}
if (len == 0)
mailidx_ctl(idx, path, IDX_EXTEND);
idx->msg = (struct idx_msg *)(idx->hdr + 1);
idx->file_fd = open(path, O_CREAT|(flags & IDX_O_APPEND ?
O_APPEND : O_RDWR), S_IRUSR|S_IWUSR);
if (idx->file_fd < 0) {
munmap((void *)idx->hdr, len);
close(fd);
fd = -1;
return(FALSE);
}
fl.l_type = 0;
fl.l_type = (flags & IDX_O_APPEND) ? F_WRLCK : fl.l_type;
fl.l_type = (flags & IDX_O_MODIFY) ? F_WRLCK : fl.l_type;
if (fl.l_type) {
result = fcntl(idx->file_fd, (flags & IDX_NONBLOCK) ?
F_SETLK : F_SETLKW, &fl);
if (result < 0) {
munmap((void *)idx->hdr, len);
close(idx->file_fd);
idx->file_fd = -1;
close(fd);
fd = -1;
return(FALSE);
}
}
stat(path, &stat_s);
idx->st_size = stat_s.st_size;
if (!(flags & IDX_O_IGNORE) && idx->st_size < idx->hdr->bytes) {
munmap((void *)idx->hdr, len);
close(idx->file_fd);
idx->file_fd = -1;
close(fd);
fd = -1;
return(FALSE);
}
break;
case IDX_EXTEND:
if (fd < 0)
return(FALSE);
if (len == 0) {
munmap((void *)idx->hdr, len);
len = sizeof(struct idx_hdr) + MAXINCR *
sizeof(struct idx_msg);
if (ftruncate(fd, len)) {
close(fd);
close(idx->file_fd);
fd = -1;
return(FALSE);
}
idx->hdr = mmap((void *)idx->hdr, len,
PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
if (idx->hdr == MAP_FAILED) {
close(fd);
close(idx->file_fd);
fd = -1;
return(FALSE);
}
idx->hdr->max = MAXINCR - 1;
idx->msg = (struct idx_msg *)(idx->hdr + 1);
break;
}
munmap(idx->hdr, len);
len += MAXINCR * sizeof(struct idx_msg);
if (ftruncate(fd, len)) {
close(fd);
close(idx->file_fd);
fd = -1;
return(FALSE);
}
idx->hdr = mmap(idx->hdr, len, PROT_WRITE|PROT_READ,
MAP_SHARED, fd, 0);
if (idx->hdr == MAP_FAILED) {
close(fd);
close(idx->file_fd);
fd = -1;
return(FALSE);
}
idx->hdr->max += MAXINCR;
idx->msg = (struct idx_msg *)(idx->hdr + 1);
break;
case IDX_CLOSE:
if (fd < 0)
return(FALSE);
finalsize = idx->hdr->max;
if (finalsize - MAXINCR > idx->hdr->count) {
finalsize = idx->hdr->count + MAXINCR;
idx->hdr->max = finalsize;
}
munmap(idx->hdr, len);
ftruncate(fd, sizeof(struct idx_hdr) + finalsize *
sizeof(struct idx_msg));
close(idx->file_fd);
close(fd);
fd = -1;
len = 0;
break;
default:
return(FALSE);
}
return(TRUE);
}
/* Open the mailbox and check the magic number. Rebuild the index if the
* header is corrupted, or if the mailfile is newer than the index, add
* the new messages to the index.
*/
int
mailidx_check_reindex(char *path)
{
int msgs, uidlen, topoffile, lastblank,
create = TRUE, addheader, headerlen;
char buffer[MAXBUFLEN + 1], uidldat[MAXBUFLEN + 1],
*header[2], *line, *p, *q, *r;
unsigned char digest[16];
struct tm tm;
time_t now;
size_t bytes, len, size;
off_t offset, buffleft;
struct mail_idx idx;
MD5_CTX context;
memset(&idx, '\0', sizeof(struct mail_idx));
if (!mailidx_ctl(&idx, path, IDX_OPEN,
IDX_NONBLOCK|IDX_O_MODIFY|IDX_O_IGNORE))
return(FALSE);
memset((void *)buffer, NULL, MAXBUFLEN);
size = bytes = idx.st_size;
p = q = buffer;
topoffile = TRUE;
lastblank = FALSE;
addheader = FALSE;
headerlen = 0;
msgs = -1;
offset = 0;
buffleft = 0;
len = 0;
/* Perhaps we just need to extend the index */
if (idx.hdr->magic == MAGIC_HDR && idx.st_size > idx.hdr->bytes) {
msgs = idx.hdr->count;
offset = idx.msg[msgs].offset + idx.msg[msgs].bytes;
bytes -= offset;
lseek(idx.file_fd, offset, SEEK_SET);
create = FALSE;
}
else if (idx.hdr->magic == MAGIC_HDR && idx.st_size == idx.hdr->bytes) {
mailidx_ctl(&idx, path, IDX_CLOSE);
return(TRUE);
}
/* There really doesn't seem to be any easy or more intuitive way
* to get the system timezone and daylight savings information
* than this round-about method.
* ctime sucks!
*/
now = time(NULL);
memcpy(&tm, localtime(&now), sizeof(struct tm));
for (;;) {
line = p;
if ((p = strchr(line, '\n')) == NULL) {
if (bytes == 0)
break;
strcpy(buffer, line);
line = buffer;
q = strchr(buffer, '\0');
buffleft = MAXBUFLEN - (q - buffer) - 1;
if (buffleft > bytes)
buffleft = bytes;
len = read(idx.file_fd, q, buffleft);
bytes -= len;
offset += (off_t)len;
q[buffleft] = '\0';
p = strchr(buffer, '\n');
}
*p++ = '\0';
if (*line == '\r') {
lastblank = TRUE;
addheader = FALSE;
continue;
}
if (addheader) {
headerlen += strlen(line);
header[msgs % 2] = xrealloc(header[msgs % 2],
headerlen + 1);
strcat(header[msgs % 2], line);
header[msgs % 2][headerlen] = '\0';
headerlen--;
}
if ((topoffile || lastblank ) && !strncmp(line, "From ", 5)) {
addheader = TRUE;
if (msgs + 1 >= idx.hdr->max)
if (!mailidx_ctl(&idx, path, IDX_EXTEND)) {
mailidx_ctl(&idx, path, IDX_CLOSE);
return(FALSE);
}
msgs++;
headerlen = strlen(line);
header[msgs % 2] = xmalloc(headerlen + 1);
strcpy(header[msgs % 2], line);
header[msgs % 2][headerlen - 1] = '\0';
headerlen -= 2;
idx.msg[msgs].flags = 0;
idx.msg[msgs].offset = offset -
(len - (line - buffer)) - (q - buffer);
if (!((r = memchr(line, ' ', line - p)) && r++ &&
(r = memchr(r, ' ', q - r)) && r++ ))
r = p;
if (!strptime(r, "%a %b %d %T %Y", &tm))
tm = *(localtime(&now));
idx.msg[msgs].d_time = mktime(&tm);
if (msgs > 0) {
idx.msg[msgs - 1].bytes = (size_t)(
idx.msg[msgs].offset -
idx.msg[msgs - 1].offset);
uidlen = snprintf(uidldat, MAXBUFLEN,
"%s%s%d",
header[(msgs - 1) % 2],
path, idx.msg[msgs - 1].bytes);
free(header[(msgs - 1) % 2]);
header[(msgs - 1) % 2] = NULL;
MD5Init(&context);
MD5Update(&context,
(unsigned char *)uidldat, uidlen);
MD5Final(digest, &context);
sprintf(idx.msg[msgs - 1].uidl, "%s",
binhex(digest, sizeof(digest)));
idx.msg[msgs - 1].magic = MAGIC_MSG;
}
topoffile = FALSE;
lastblank = FALSE;
}
}
if (msgs > -1) {
idx.msg[msgs].bytes = size - idx.msg[msgs].offset;
uidlen = snprintf(uidldat, MAXBUFLEN, "%s%s%d",
header[msgs % 2], path, idx.msg[msgs].bytes);
free(header[msgs % 2]);
header[msgs % 2] = NULL;
MD5Init(&context);
MD5Update(&context, (unsigned char *)uidldat, uidlen);
MD5Final(digest, &context);
sprintf(idx.msg[msgs].uidl, "%s",
binhex(digest, sizeof(digest)));
idx.msg[msgs].magic = MAGIC_MSG;
}
idx.hdr->count = msgs;
idx.hdr->bytes = size;
/* Don't blat things if we didn't create the index */
if (create) {
idx.hdr->magic = MAGIC_HDR;
idx.hdr->flags = IDX_F_NULL;
idx.hdr->expire = 0;
idx.hdr->remove = 0;
}
mailidx_ctl(&idx, path, IDX_CLOSE);
return(TRUE);
}
static int
mailidx_get_message(struct mail_idx *idx, struct mbox_idx *mbox, int number)
{
static char buffer[MAXBUFLEN];
int len, toread;
sendline(SEND_FLUSH, "+OK sending message ending with a '.' on "
"a line by itself");
toread = idx->msg[mbox->idx_num[number]].bytes;
lseek(idx->file_fd, idx->msg[mbox->idx_num[number]].offset, SEEK_SET);
while (toread > 0) {
if (toread > MAXBUFLEN)
len = read(idx->file_fd, buffer, MAXBUFLEN);
else
len = read(idx->file_fd, buffer, toread);
xwrite(buffer, len);
toread -= len;
}
mbox->idx_flags[number] |= IDX_F_READ;
sendline(SEND_FLUSH, ".");
return(TRUE);
}
/* Seek to the begining of the mail message and read the message
* in MAXBUFLEN chunks. Write out n lines of the message in as large chunks
* as possible.
*/
static int
mailidx_get_message_top(struct mail_idx *idx, struct mbox_idx *mbox,
int number, int lines)
{
char buffer[MAXBUFLEN], *p;
int inbody, count, bytes, len, buffleft;
char *line, *offset;
p = buffer;
inbody = FALSE;
count = 0;
bytes = idx->msg[mbox->idx_num[number]].bytes;
lseek(idx->file_fd, idx->msg[mbox->idx_num[number]].offset, SEEK_SET);
sendline(SEND_FLUSH, "+OK sending message ending with a '.' on "
"a line by itself");
memset((void *)buffer, NULL, MAXBUFLEN);
for (;;) {
line = p;
if ((p = strchr(line, '\n')) == NULL) {
xwrite(buffer, line - buffer);
if (bytes == 0 || (inbody && count > lines))
break;
strcpy(buffer, line);
line = buffer;
offset = strchr(buffer, '\0');
buffleft = MAXBUFLEN - (offset - buffer) - 1;
if (buffleft > bytes)
buffleft = bytes;
len = read(idx->file_fd, offset, buffleft);
bytes -= len;
offset[buffleft] = '\0';
p = strchr(buffer, '\n');
}
if (inbody && count > lines) {
xwrite(buffer, line - buffer);
break;
}
p++;
if (!inbody && *p == '\r')
inbody = TRUE;
if (inbody == TRUE)
count++;
}
sendline(SEND_FLUSH, ".");
mbox->idx_flags[number] |= IDX_F_TOP;
return(TRUE);
}
/* XXXX Note to self Must flasify UIDL info for old messages.
*/
static int
mailidx_get_uidl_all(struct mail_idx *idx, struct mbox_idx *mbox)
{
int i;
sendline(SEND_BUF, "+OK sending list ending with a '.' on "
"a line by itself");
for (i = 0; i <= mbox->count; i++) {
if (mbox->idx_flags[i] & IDX_F_DELETE)
continue;
sendline(SEND_BUF, "%d %s", i + 1,
idx->msg[mbox->idx_num[i]].uidl);
}
return(TRUE);
}
/* XXXX Note to self Must flasify UIDL info for old messages.
*/
static int
mailidx_list_all(struct mail_idx *idx, struct mbox_idx *mbox)
{
int i;
sendline(SEND_BUF, "+OK sending list ending with a '.' on "
"a line by itself");
for (i = 0; i <= mbox->count; i++) {
if (mbox->idx_flags[i] & IDX_F_DELETE)
continue;
sendline(SEND_BUF, "%d %d", i + 1,
idx->msg[mbox->idx_num[i]].bytes);
}
return(TRUE);
}
static int
mailidx_is_message(struct mbox_idx *mbox, int number)
{
if (number > mbox->count || number < 0) {
message(NOSUCH);
return(FALSE);
}
if (mbox->idx_flags[number] & IDX_F_DELETE) {
message(ALREADYDELETED);
return(FALSE);
}
return(TRUE);
}
/* Open the mailbox and cache the message flags in our internal structure
* so that updates don't happen live and are only committed at the end
* of the session
*/
static int
mailidx_open(struct mail_idx *idx, struct mbox_idx *mbox, char *path)
{
int i, count;
memset(idx, '\0', sizeof(struct mail_idx));
mailidx_check_reindex(path);
if (!mailidx_ctl(idx, path, IDX_OPEN, IDX_NONBLOCK|IDX_O_READ))
return(FALSE);
if (idx->hdr->magic != MAGIC_HDR) {
mailidx_ctl(idx, path, IDX_CLOSE);
return(FALSE);
}
mbox->idx_num = xcalloc(idx->hdr->count + 2, sizeof(int));
mbox->idx_flags = xcalloc(idx->hdr->count + 2, sizeof(int));
for (i = 0, count = 0; i <= idx->hdr->count; i++) {
if (idx->msg[i].magic != MAGIC_MSG)
continue;
if (idx->msg[i].flags & IDX_F_DELETE)
continue;
mbox->idx_num[count] = i;
mbox->idx_flags[count++] = idx->msg[i].flags;
mbox->bytes += idx->msg[i].bytes;
mbox->expire += (idx->msg[i].flags & IDX_F_EXPIRE) > 0;
mbox->remove += (idx->msg[i].flags & IDX_F_REMOVE) > 0;
}
mbox->count = count - 1;
if (mbox->count > 0)
/* Should really use mergesort() here because it is stable
* however not everyone has mergesort().
*/
qsort((void *)idx->msg, mbox->count + 1,
sizeof(struct idx_msg), &compar);
return(TRUE);
}
static int
compar(const void *a, const void *b)
{
if (flags & MAILBOX_F_SORT_TIME)
/* effectively don't sort */
return(0);
else if (flags & MAILBOX_F_SORT_SIZE)
return(((struct idx_msg *)a)->bytes -
((struct idx_msg *)b)->bytes);
else
/* effectively don't sort */
return(0);
}
/* Commit the buffered copy of message flags to the index.
* Update the global flag data as well to optomise deciding
* whether compaction is required.
*/
static void
mailidx_close(struct mail_idx *idx, struct mbox_idx *mbox, char *path)
{
int i;
for (i = 0; i <= mbox->count; i++) {
if ((idx->msg[mbox->idx_num[i]].flags & mbox->idx_flags[i]) !=
mbox->idx_flags[i])
idx->msg[mbox->idx_num[i]].flags |= mbox->idx_flags[i];
if ((idx->hdr->flags & mbox->idx_flags[i]) !=
mbox->idx_flags[i])
idx->hdr->flags |= mbox->idx_flags[i];
}
free(mbox->idx_num);
free(mbox->idx_flags);
if (mbox->expire > idx->hdr->expire)
idx->hdr->expire = mbox->expire;
if (mbox->remove > idx->hdr->remove)
idx->hdr->remove = mbox->remove;
mailidx_ctl(idx, path, IDX_CLOSE);
}
/* Skip through the index and remove all mail flagged IDX_F_EXPIRE & IDX_F_READ
* IDX_F_REMOVE or IDX_F_DELETE. Compact the text file and index.
*/
int
mailidx_compact(struct connection *cxn)
{
struct mail_idx idx;
struct idx_msg *idx_src, *idx_dst;
char buffer[MAXBUFLEN];
off_t mbx_src, mbx_dst;
size_t left, bytes, len, hdr_bytes;
int i, ret, del, exp, rem, errors, count, facility;
facility = LOG_INFO;
memset(&idx, '\0', sizeof(struct mail_idx));
if (!mailidx_ctl(&idx, cxn->mailpath, IDX_OPEN,
IDX_NONBLOCK|IDX_O_MODIFY)) {
syslog(facility, "%s: Unable to lock mailbox for compaction",
cxn->auth_string);
return(FALSE);
}
/* Make sure there is at least 1 expired/remove/deleted message */
if (!((cxn->flags & MAILBOX_F_AUTODELETE &&
(idx.hdr->flags & IDX_F_EXPIRE) &&
(idx.hdr->flags & IDX_F_READ)) ||
(idx.hdr->flags & IDX_F_REMOVE) ||
(idx.hdr->flags & IDX_F_DELETE))) {
mailidx_ctl(&idx, cxn->mailpath, IDX_CLOSE);
return(TRUE);
}
ret = del = exp = rem = errors = 0;
hdr_bytes = idx.hdr->bytes;
idx_dst = NULL;
idx_src = NULL;
mbx_dst = 0;
count = idx.hdr->count;
for (i = 0; i <= count; i++) {
/* Find the first deleted message and skip through
* subsequent deleted messages.
*/
if (idx.msg[i].flags & IDX_F_DELETE) {
del++;
if (idx_dst == NULL) {
idx_dst = &idx.msg[i];
mbx_dst = idx.msg[i].offset;
}
idx.hdr->bytes -= idx.msg[i].bytes;
idx.hdr->count--;
}
else if (cxn->flags & MAILBOX_F_AUTODELETE &&
(idx.msg[i].flags & IDX_F_EXPIRE) &&
(idx.msg[i].flags & IDX_F_READ)) {
exp++;
if (idx_dst == NULL) {
idx_dst = &idx.msg[i];
mbx_dst = idx.msg[i].offset;
}
idx.hdr->bytes -= idx.msg[i].bytes;
idx.hdr->count--;
}
else if (cxn->remove && idx.msg[i].flags & IDX_F_REMOVE) {
rem++;
if (idx_dst == NULL) {
idx_dst = &idx.msg[i];
mbx_dst = idx.msg[i].offset;
}
idx.hdr->bytes -= idx.msg[i].bytes;
idx.hdr->count--;
}
else {
if (idx_dst == NULL)
continue;
/* If we get here, idx_dst and mbx_dst point to the
* first deleted message. Now we need to find the next
* deleted message or EOF and move that chunk of the
* mailbox to mbx_dst.
*/
bytes = 0;
mbx_src = idx.msg[i].offset;
idx_src = &idx.msg[i];
for (; i <= count; i++) {
if ((cxn->flags & MAILBOX_F_AUTODELETE &&
(idx.msg[i].flags & IDX_F_EXPIRE) &&
(idx.msg[i].flags & IDX_F_READ)) ||
(cxn->remove && idx.msg[i].flags &
IDX_F_REMOVE) ||
(idx.msg[i].flags & IDX_F_DELETE)) {
i--;
break;
}
bytes += idx.msg[i].bytes;
}
for (; idx_src <= &idx.msg[i]; idx_src++, idx_dst++) {
memcpy(idx_dst, idx_src,
sizeof(struct idx_msg));
idx_dst->offset -= mbx_src - mbx_dst;
}
left = bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
lseek(idx.file_fd, mbx_src, SEEK_SET);
while ((len = read(idx.file_fd, buffer, left)) > 0) {
lseek(idx.file_fd, mbx_dst, SEEK_SET);
write(idx.file_fd, buffer, len);
mbx_src += len;
mbx_dst += len;
bytes -= len;
left = bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
if (left < 1)
break;
lseek(idx.file_fd, mbx_src, SEEK_SET);
}
}
}
if ((del || exp || rem) && hdr_bytes < idx.st_size) {
mbx_src = hdr_bytes;
left = bytes = idx.st_size - hdr_bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
lseek(idx.file_fd, mbx_src, SEEK_SET);
while ((len = read(idx.file_fd, buffer, left)) > 0) {
lseek(idx.file_fd, mbx_dst, SEEK_SET);
write(idx.file_fd, buffer, len);
mbx_src += len;
mbx_dst += len;
bytes -= len;
left = bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
if (left < 1)
break;
lseek(idx.file_fd, mbx_src, SEEK_SET);
}
}
ftruncate(idx.file_fd, (off_t)idx.hdr->bytes +
(idx.st_size - hdr_bytes));
idx.hdr->flags = IDX_F_NULL;
log_stats(cxn->auth_string, ret, idx.hdr->count + 1,
idx.hdr->bytes, errors, del, idx.hdr->expire, exp,
idx.hdr->remove, rem);
mailidx_ctl(&idx, cxn->mailpath, IDX_CLOSE);
return(TRUE);
}
int
mailidx_mbox_op(struct connection *cxn, enum cmd cmd, ...)
{
static struct mail_idx idx;
static struct mbox_idx mbox;
va_list ap;
int arg1, arg2, i, bytes, msgs;
switch (cmd) {
case SESSION_START:
flags = cxn->flags;
if (mailidx_open(&idx, &mbox, cxn->mailpath)) {
bulletin_mbox_op(cxn, SESSION_START);
sendline(SEND_FLUSH, "+OK %d message%s (%d octets) "
"(Expire on RETR: %d Remove on QUIT: %d)",
bulletin_mbox_op(cxn, BUL_MSGS) + mbox.count + 1,
bulletin_mbox_op(cxn, BUL_MSGS) + mbox.count + 1 ==
1 ? "" : "s",
mbox.bytes + bulletin_mbox_op(cxn, BUL_SIZE),
mbox.expire, mbox.remove);
return(TRUE);
}
return(FALSE);
break;
case SESSION_END:
mailidx_close(&idx, &mbox, cxn->mailpath);
mailidx_compact(cxn);
return(TRUE);
case DELE:
va_start(ap, cmd);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 - 1 > mbox.count) {
bulletin_mbox_op(cxn, DELE, mbox.count + 1, arg1);
break;
}
if (!mailidx_is_message(&mbox, arg1 - 1))
return(FALSE);
mbox.idx_flags[arg1 - 1] |= IDX_F_DELETE;
sendline(SEND_FLUSH, "+OK message deleted");
break;
case LIST:
va_start(ap, cmd);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 >= 0) {
if (arg1 - 1 > mbox.count) {
bulletin_mbox_op(cxn, LIST, mbox.count + 1,
arg1);
break;
}
if (!mailidx_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %d", arg1,
idx.msg[mbox.idx_num[arg1 - 1]].bytes);
}
else {
mailidx_list_all(&idx, &mbox);
bulletin_mbox_op(cxn, LIST, mbox.count + 1, arg1);
sendline(SEND_FLUSH, ".");
}
break;
case NOOP:
sendline(SEND_FLUSH, "+OK");
break;
case QUIT:
sendline(SEND_FLUSH, "+OK");
poputil_end();
mailidx_mbox_op(cxn, SESSION_END);
bulletin_mbox_op(cxn, SESSION_END);
exit(EX_OK);
break;
case RETR:
va_start(ap, cmd);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 - 1 > mbox.count) {
bulletin_mbox_op(cxn, RETR, mbox.count + 1, arg1);
break;
}
if (!mailidx_is_message(&mbox, arg1 - 1))
return(FALSE);
if (idx.last < arg1)
idx.last = arg1;
mailidx_get_message(&idx, &mbox, arg1 - 1);
break;
case RSET:
for (i = 0; i < mbox.count; i++)
mbox.idx_flags[i] &= !(IDX_F_DELETE | IDX_F_READ);
idx.last = 0;
bulletin_mbox_op(cxn, RSET);
sendline(SEND_FLUSH, "+OK");
break;
case STAT:
bytes = msgs = 0;
for (i = 0; i <= mbox.count; i++) {
if (idx.msg[mbox.idx_num[i]].flags & IDX_F_DELETE)
continue;
bytes += idx.msg[mbox.idx_num[i]].bytes;
msgs++;
}
msgs += bulletin_mbox_op(cxn, BUL_MSGS);
bytes += bulletin_mbox_op(cxn, BUL_SIZE);
sendline(SEND_FLUSH, "+OK %d %d", msgs, bytes);
break;
case TOP:
va_start(ap, cmd);
arg1 = va_arg(ap, int);
arg2 = va_arg(ap, int);
va_end(ap);
if (arg1 - 1 > mbox.count) {
bulletin_mbox_op(cxn, TOP, mbox.count + 1, arg1, arg2);
break;
}
if (!mailidx_is_message(&mbox, arg1 - 1))
return(FALSE);
mailidx_get_message_top(&idx, &mbox, arg1 - 1, arg2);
break;
case UIDL:
va_start(ap, cmd);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 >= 0) {
if (arg1 - 1 > mbox.count) {
bulletin_mbox_op(cxn, UIDL, mbox.count + 1,
arg1);
break;
}
if (!mailidx_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %s", arg1,
idx.msg[mbox.idx_num[arg1 - 1]].uidl);
}
else {
mailidx_get_uidl_all(&idx, &mbox);
bulletin_mbox_op(cxn, UIDL, mbox.count + 1, arg1);
sendline(SEND_FLUSH, ".");
}
break;
case LAST: /* backwards compatibility with RFC1460 */
sendline(SEND_FLUSH, "+OK %d", idx.last);
break;
case BUL_MSGS: /* not reached */
case BUL_SIZE:
case APOP:
case AUTH:
case PASS:
case USER:
case TIMEDOUT:
case INVALCMD:
break;
}
return(FALSE);
}
syntax highlighted by Code2HTML, v. 0.9.1