/*
* 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_mbf.c,v 1.20 2003/03/03 12:10:19 ianf Exp $
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <netinet/in.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 <unistd.h>
#include "poputil.h"
#include "private.h"
#define MBF_F_OK 0x0001
#define MBF_F_NOSUCH 0x0002
#define MBF_F_DELETE 0x0004
#define MBF_F_EXPIRE 0x0008
#define MBF_F_REMOVE 0x0010
#define MBF_F_READ 0x0020
#define MBF_F_TOP 0x0040
#define MBF_F_STAT 0x0080
/* Need this global variable to get configuration parameters to the compar()
* routine
*/
static int flags;
struct message {
time_t d_time;
off_t offset;
char uidl[33];
char *header;
size_t bytes;
int flags;
};
struct mbox {
int fd;
size_t bytes;
int count;
int max;
int expire;
int remove;
struct message *msg;
};
static int compar(const void *, const void *);
static void moremessages(struct mbox *);
/* Read the mailbox and rebuild the index file.
*/
static int
mbf_open(struct mbox *mbox, struct connection *cxn)
{
int len, offset, msgs, topoffile, lastblank,
addheader, headerlen = 0;
char buffer[MAXBUFLEN + 1], uidldat[MAXBUFLEN + 1],
*line, *p, *q, *r;
unsigned char digest[16];
struct tm tm;
time_t now;
size_t buffleft, bytes, size, uidlen;
struct stat stat_s;
MD5_CTX context;
memset(mbox, '\0', sizeof(struct mbox));
if (stat(cxn->mailpath, &stat_s) < 0){
mbox->count = -1;
mbox->bytes = 0;
mbox->fd = -1;
return(TRUE);
}
mbox->fd = openlock(cxn->mailpath, O_RDWR|O_NONBLOCK);
if (mbox->fd < 0)
return(FALSE);
size = bytes = (size_t)stat_s.st_size;
memset(buffer, NULL, MAXBUFLEN);
p = q = buffer;
topoffile = TRUE;
lastblank = FALSE;
addheader = FALSE;
msgs = -1;
offset = 0;
len = 0;
/* 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(mbox->fd, q, buffleft);
bytes -= len;
offset += len;
q[buffleft] = '\0';
p = strchr(buffer, '\n');
}
*p++ = '\0';
if (*line == '\0') {
lastblank = TRUE;
addheader = FALSE;
continue;
}
if (addheader) {
headerlen += strlen(line);
mbox->msg[msgs].header = realloc(mbox->msg[msgs].header,
headerlen + 1);
strcat(mbox->msg[msgs].header, line);
}
if ((topoffile || lastblank ) && !strncmp(line, "From ", 5)) {
topoffile = FALSE;
addheader = TRUE;
if (msgs + 1 >= mbox->max)
moremessages(mbox);
msgs++;
mbox->msg[msgs].header = NULL;
mbox->msg[msgs].flags = 0;
mbox->msg[msgs].offset = offset -
(len - (line - buffer)) - (q - buffer);
if (!((r = memchr(line, ' ', (size_t)(line - p))) &&
r++ &&
(r = memchr(r, ' ', (size_t)(q - r))) && r++ ))
r = p;
if (!strptime(r, "%a %b %d %T %Y", &tm))
tm = *(localtime(&now));
mbox->msg[msgs].d_time = mktime(&tm);
if (cxn->expire && (time((time_t *)NULL) -
mbox->msg[msgs].d_time) > cxn->expire) {
mbox->msg[msgs].flags |= MBF_F_EXPIRE;
mbox->expire++;
}
if (cxn->remove && (time((time_t *)NULL) -
mbox->msg[msgs].d_time) > cxn->remove) {
mbox->msg[msgs].flags |= MBF_F_REMOVE;
mbox->remove++;
}
headerlen = strlen(line);
mbox->msg[msgs].header = xmalloc(headerlen + 1);
strcpy(mbox->msg[msgs].header, line);
if (msgs > 0) {
mbox->msg[msgs - 1].bytes =
(size_t)mbox->msg[msgs].offset -
(size_t)mbox->msg[msgs - 1].offset;
uidlen = snprintf(uidldat, MAXBUFLEN,
"%s%s%s%d", mbox->msg[msgs - 1].flags &
(MBF_F_EXPIRE | MBF_F_REMOVE) &&
cxn->flags & MAILBOX_F_FALSEUIDL ?
"1" : "0", cxn->mailpath,
mbox->msg[msgs - 1].header,
mbox->msg[msgs - 1].bytes);
free(mbox->msg[msgs - 1].header);
mbox->msg[msgs - 1].header = NULL;
MD5Init(&context);
MD5Update(&context,
(unsigned char *)uidldat, (size_t)uidlen);
MD5Final(digest, &context);
strcpy(mbox->msg[msgs - 1].uidl, binhex(digest,
sizeof(digest)));
}
}
lastblank = FALSE;
}
if (msgs > -1) {
mbox->msg[msgs].bytes = size - (size_t)mbox->msg[msgs].offset;
uidlen = snprintf(uidldat, MAXBUFLEN, "%s%s%s%d",
mbox->msg[msgs].flags & (MBF_F_EXPIRE | MBF_F_REMOVE) &&
cxn->flags & MAILBOX_F_FALSEUIDL ? "1" : "0", cxn->mailpath,
mbox->msg[msgs].header,
mbox->msg[msgs].bytes);
free(mbox->msg[msgs].header);
mbox->msg[msgs].header = NULL;
MD5Init(&context);
MD5Update(&context, (unsigned char *)uidldat, (size_t)uidlen);
MD5Final(digest, &context);
strcpy(mbox->msg[msgs].uidl, binhex(digest, sizeof(digest)));
}
mbox->count = msgs;
mbox->bytes = size;
if (mbox->count > 0)
/* Should really use mergesort() here because it is stable
* however not everyone has mergesort().
*/
qsort((void *)mbox->msg, mbox->count + 1,
sizeof(struct message), &compar);
return(TRUE);
}
static int
compar(const void *a, const void *b)
{
if (flags & MAILBOX_F_SORT_TIME)
return(((struct message *)a)->offset -
((struct message *)b)->offset);
else if (flags & MAILBOX_F_SORT_SIZE)
return(((struct message *)a)->bytes -
((struct message *)b)->bytes);
else
/* effectively don't sort */
return(0);
}
void
mbf_close(struct mbox *mbox, struct connection *cxn)
{
int i, ret, del, exp, rem, errors, facility;
off_t src, dst;
size_t left, bytes, len;
char buffer[MAXBUFLEN];
facility = LOG_INFO;
flags = (flags & ~MAILBOX_F_SORTMASK) | MAILBOX_F_SORT_TIME;
qsort((void *)mbox->msg, mbox->count + 1,
sizeof(struct message), &compar);
ret = del = exp = rem = errors = 0;
dst = -1;
for (i = 0; i <= mbox->count; i++) {
ret += (mbox->msg[i].flags & MBF_F_READ) > 1;
if (mbox->msg[i].flags & MBF_F_DELETE) {
del++;
mbox->bytes -= mbox->msg[i].bytes;
if (dst < 0)
dst = mbox->msg[i].offset;
}
else if (cxn->flags & MAILBOX_F_AUTODELETE &&
mbox->msg[i].flags & MBF_F_EXPIRE &&
mbox->msg[i].flags & MBF_F_READ) {
exp++;
mbox->bytes -= mbox->msg[i].bytes;
if (dst < 0)
dst = mbox->msg[i].offset;
}
else if (mbox->msg[i].flags & MBF_F_REMOVE) {
rem++;
mbox->bytes -= mbox->msg[i].bytes;
if (dst < 0)
dst = mbox->msg[i].offset;
}
else {
if (dst < 0)
continue;
bytes = 0;
src = mbox->msg[i].offset;
for (; i <= mbox->count; i++) {
if ((mbox->msg[i].flags & MBF_F_DELETE) ||
(cxn->flags & MAILBOX_F_AUTODELETE &&
mbox->msg[i].flags & MBF_F_EXPIRE &&
mbox->msg[i].flags & MBF_F_READ) ||
(mbox->msg[i].flags & MBF_F_REMOVE)) {
i--;
break;
}
bytes += mbox->msg[i].bytes;
}
left = bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
lseek(mbox->fd, src, SEEK_SET);
while ((len =
read(mbox->fd, buffer, left)) > 0) {
lseek(mbox->fd, dst, SEEK_SET);
write(mbox->fd, buffer, len);
src += len;
dst += len;
lseek(mbox->fd, src, SEEK_SET);
bytes -= len;
left = bytes;
if (left > MAXBUFLEN)
left = MAXBUFLEN;
}
}
}
free(mbox->msg);
ftruncate(mbox->fd, (off_t)mbox->bytes);
close(mbox->fd);
log_stats(cxn->auth_string, ret, mbox->count + 1 - del - exp - rem,
mbox->bytes, errors, del, mbox->expire, exp, mbox->remove, rem);
}
static int
mbf_is_message(struct mbox *mbox, int number)
{
if (number > mbox->count || number < 0) {
message(NOSUCH);
return(FALSE);
}
if (mbox->msg[number].flags & MBF_F_DELETE) {
message(ALREADYDELETED);
return(FALSE);
}
return(TRUE);
}
int
mbf_get_message_lines(struct mbox *mbox, int number, int lines)
{
char buffer[MAXBUFLEN], *p;
int inbody, count, len;
char *line, *offset;
size_t bytes, buffleft;
p = buffer;
inbody = FALSE;
count = 0;
bytes = mbox->msg[number].bytes;
lseek(mbox->fd, mbox->msg[number].offset, SEEK_SET);
sendline(SEND_BUF, "+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) {
if (bytes == 0 || (inbody && lines > -1 &&
count > lines)) {
break;
}
strcpy(buffer, line);
line = buffer;
offset = strchr(buffer, '\0');
buffleft = MAXBUFLEN - (offset - buffer) - 1;
if (buffleft > bytes)
buffleft = bytes;
len = read(mbox->fd, offset, buffleft);
bytes -= len;
offset[len] = '\0';
p = strchr(buffer, '\n');
}
*p++ = '\0';
if (line[0] == '.' && line[1] == '\0') {
sendline(SEND_BUF, "..");
}
else {
sendline(SEND_BUF, "%s", line);
}
if (inbody && lines > -1 && count > lines) {
break;
}
if (!inbody && *p == '\n')
inbody = TRUE;
if (inbody == TRUE)
count++;
}
sendline(SEND_FLUSH, ".");
if (lines == -1)
mbox->msg[number].flags |= MBF_F_READ;
else
mbox->msg[number].flags |= MBF_F_TOP;
return(TRUE);
}
static void
moremessages(struct mbox *mbox)
{
mbox->msg = xrealloc(mbox->msg,
(mbox->max += MAXINCR) * sizeof(struct message));
}
static int
mbf_get_uidl_all(struct mbox *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->msg[i].flags & MBF_F_DELETE)
continue;
sendline(SEND_BUF, "%d %s", i + 1, mbox->msg[i].uidl);
}
return(TRUE);
}
static int
mbf_list_all(struct mbox *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->msg[i].flags & MBF_F_DELETE)
continue;
sendline(SEND_BUF, "%d %d", i + 1, mbox->msg[i].bytes);
}
return(TRUE);
}
int
mbf_mbox_op(struct connection *cxn, enum cmd cmd, ...)
{
static struct mbox mbox;
va_list ap;
int arg1, arg2, i, bytes, msgs;
static int last = 0;
switch (cmd) {
case SESSION_START:
flags = cxn->flags;
if (mbf_open(&mbox, cxn)) {
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);
case SESSION_END:
mbf_close(&mbox, 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 (!mbf_is_message(&mbox, arg1 - 1))
return(FALSE);
mbox.msg[arg1 - 1].flags |= MBF_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 (!mbf_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %d", arg1,
mbox.msg[arg1 - 1].bytes);
}
else {
mbf_list_all(&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();
mbf_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 (!mbf_is_message(&mbox, arg1 - 1))
return(FALSE);
if (last < arg1)
last = arg1;
mbf_get_message_lines(&mbox, arg1 - 1, -1);
break;
case RSET:
for (i = 0; i <= mbox.count; i++)
mbox.msg[i].flags &= !(MBF_F_DELETE | MBF_F_READ);
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 (mbox.msg[i].flags & MBF_F_DELETE)
continue;
bytes += mbox.msg[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 (!mbf_is_message(&mbox, arg1 - 1))
return(FALSE);
mbf_get_message_lines(&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 (!mbf_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %s", arg1,
mbox.msg[arg1 - 1].uidl);
}
else {
mbf_get_uidl_all(&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", 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