/*
* 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_maildir.c,v 1.32 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>
#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"
extern int fd_in, fd_out;
/*
* MailDir Format
*/
#define MDIR_F_OK 0x0001
#define MDIR_F_NOSUCH 0x0002
#define MDIR_F_DELETE 0x0004
#define MDIR_F_EXPIRE 0x0008
#define MDIR_F_REMOVE 0x0010
#define MDIR_F_READ 0x0020
#define MDIR_F_TOP 0x0040
#define MDIR_F_STAT 0x0080
#define MDIR_F_NEW 0x0100
/* Need this global variable to get configuration parameters to the compar()
* routine
*/
static int flags;
struct message {
char *directory;
char *filename;
char *path;
char uidl[33];
time_t d_time;
size_t bytes;
int flags;
};
struct mbox {
int fd;
char *new_dir;
char *cur_dir;
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 *);
static int maildir_open(struct mbox *, struct connection *);
static int maildir_is_message(struct mbox *, int);
static int maildir_list_all(struct mbox *);
static int maildir_get_uidl_all(struct mbox *);
static int maildir_get_message_lines(struct mbox *, int, int);
static void maildir_close(struct mbox *, struct connection *);
static int
maildir_open(struct mbox *mbox, struct connection *cxn)
{
DIR *dirp;
struct dirent *dp;
struct stat stat_s;
char *dir, uidldat[MAXBUFLEN + 1];
unsigned char digest[16];
int pathlen, uidlen, n, dirs, exists = FALSE;
MD5_CTX context;
memset(mbox, '\0', sizeof(struct mbox));
mbox->count = -1;
mbox->max = -1;
if (cxn->flags & MAILBOX_F_MAILDIRCOMPAT) {
dirs = 0;
mbox->new_dir = xmalloc(strlen(cxn->mailpath) + 2);
strcpy(mbox->new_dir, cxn->mailpath);
strcat(mbox->new_dir, "/");
}
else {
dirs = 1;
mbox->new_dir = xmalloc(strlen(cxn->mailpath) + 5);
strcpy(mbox->new_dir, cxn->mailpath);
strcat(mbox->new_dir, "/new/");
mbox->cur_dir = xmalloc(strlen(cxn->mailpath) + 5);
strcpy(mbox->cur_dir, cxn->mailpath);
strcat(mbox->cur_dir, "/cur/");
}
for (n = 0; n <= dirs; n++) {
if (n == 0) {
dir = mbox->new_dir;
}
else {
dir = mbox->cur_dir;
}
pathlen = strlen(dir);
if ((dirp = opendir(dir))) {
exists = TRUE;
while ((dp = readdir(dirp)) != NULL) {
if (!strncmp(dp->d_name, ".", 1) ||
!strncmp(dp->d_name, "..", 2))
continue;
if (mbox->count + 1 >= mbox->max)
moremessages(mbox);
mbox->count++;
mbox->msg[mbox->count].flags = FALSE;
mbox->msg[mbox->count].directory = dir;
mbox->msg[mbox->count].filename =
xmalloc(strlen(dp->d_name) + 1);
strcpy(mbox->msg[mbox->count].filename,
dp->d_name);
mbox->msg[mbox->count].path =
xmalloc(pathlen + strlen(dp->d_name) + 1);
strcpy(mbox->msg[mbox->count].path, dir);
strcat(mbox->msg[mbox->count].path, dp->d_name);
stat(mbox->msg[mbox->count].path, &stat_s);
mbox->msg[mbox->count].d_time = stat_s.st_mtime;
mbox->msg[mbox->count].bytes = stat_s.st_size;
mbox->bytes += stat_s.st_size;
if (cxn->expire && (time((time_t *)NULL) -
stat_s.st_mtime) > cxn->expire) {
mbox->msg[mbox->count].flags |=
MDIR_F_EXPIRE;
mbox->expire++;
}
if (cxn->remove && (time((time_t *)NULL) -
stat_s.st_mtime) > cxn->remove) {
mbox->msg[mbox->count].flags |=
MDIR_F_REMOVE;
mbox->remove++;
}
if (n == 0)
mbox->msg[mbox->count].flags |=
MDIR_F_NEW;
uidlen = snprintf(uidldat, MAXBUFLEN,
"%d%s%d", mbox->msg[mbox->count].flags &
(MDIR_F_EXPIRE | MDIR_F_REMOVE) &&
cxn->flags & MAILBOX_F_FALSEUIDL ?
time(NULL) : mbox->msg[mbox->count].d_time,
dp->d_name,
mbox->msg[mbox->count].bytes);
MD5Init(&context);
MD5Update(&context, (unsigned char *)uidldat,
(size_t)uidlen);
MD5Final(digest, &context);
strcpy(mbox->msg[mbox->count].uidl,
binhex(digest, sizeof(digest)));
}
closedir(dirp);
}
}
if (exists) {
if (mbox->count >= 0)
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)->d_time -
((struct message *)b)->d_time);
else if (flags & MAILBOX_F_SORT_SIZE)
return(((struct message *)a)->bytes -
((struct message *)b)->bytes);
else
return(0);
}
static void
maildir_close(struct mbox *mbox, struct connection *cxn)
{
int i, ret, del, exp, rem, errors;
char *path = NULL;
ret = del = exp = rem = errors = 0;
for (i = 0; i <= mbox->count; i++) {
ret += (mbox->msg[i].flags & MDIR_F_READ) / MDIR_F_READ;
if (mbox->msg[i].flags & MDIR_F_DELETE) {
if (unlink(mbox->msg[i].path) != NULL) {
syslog(LOG_NOTICE, "Problem deleting %s "
"(error: %s)",
mbox->msg[i].path, strerror(errno));
errors++;
free(mbox->msg[i].path);
continue;
}
del++;
mbox->bytes -= mbox->msg[i].bytes;
free(mbox->msg[i].path);
continue;
}
else if (cxn->flags & MAILBOX_F_AUTODELETE &&
mbox->msg[i].flags & MDIR_F_EXPIRE &&
mbox->msg[i].flags & MDIR_F_READ) {
if (unlink(mbox->msg[i].path) != NULL) {
syslog(LOG_NOTICE, "Problem expiring %s "
"(error: %s)",
mbox->msg[i].path, strerror(errno));
errors++;
free(mbox->msg[i].path);
continue;
}
exp++;
mbox->bytes -= mbox->msg[i].bytes;
free(mbox->msg[i].path);
continue;
}
else if (mbox->msg[i].flags & MDIR_F_REMOVE) {
if (unlink(mbox->msg[i].path) != NULL) {
syslog(LOG_NOTICE, "Problem removing %s "
"(error: %s)",
mbox->msg[i].path, strerror(errno));
errors++;
free(mbox->msg[i].path);
continue;
}
rem++;
mbox->bytes -= mbox->msg[i].bytes;
free(mbox->msg[i].path);
continue;
}
else if (!(cxn->flags & MAILBOX_F_MAILDIRCOMPAT) &&
mbox->msg[i].flags & MDIR_F_READ &&
mbox->msg[i].flags & MDIR_F_NEW) {
path = xrealloc(path, strlen(mbox->msg[i].filename) +
strlen(mbox->cur_dir) + 1);
strcpy(path, mbox->cur_dir);
strcat(path, mbox->msg[i].filename);
if (link(mbox->msg[i].path, path) == 0)
unlink(mbox->msg[i].path);
}
}
log_stats(cxn->auth_string, ret, mbox->count + 1 - del - exp - rem,
mbox->bytes, errors, del, mbox->expire, exp, mbox->remove, rem);
free(mbox->msg);
free(path);
}
static int
maildir_get_message_lines(struct mbox *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 = mbox->msg[number].bytes;
mbox->fd = openlock(mbox->msg[number].path, O_RDWR|O_NONBLOCK);
if (mbox->fd < 0) {
sendline(SEND_FLUSH, "-ERR error opening message: %s",
strerror(errno));
return(FALSE);
}
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[buffleft] = '\0';
p = strchr(buffer, '\n');
}
if (p == NULL) {
sendline(SEND_BUF, "");
sendline(SEND_BUF, "-------------------------------------------");
sendline(SEND_BUF, "The POP server encountered a line way in");
sendline(SEND_BUF, "excess of 988 characters at this point in");
sendline(SEND_BUF, "the message '%s'.", mbox->msg[number].path);
sendline(SEND_BUF, "This is in violation of RFC2822 section");
sendline(SEND_BUF, "2.1.1 .");
sendline(SEND_BUF, "");
sendline(SEND_BUF, "This is not the end of the actual message.");
sendline(SEND_BUF, "Ask the sender to resend the message in a");
sendline(SEND_BUF, "manner conforming to RFC2822.");
sendline(SEND_BUF, "-------------------------------------------");
break;
}
*p++ = '\0';
if (line[0] == '.' && line[1] == '\0') {
sendline(SEND_BUF, "..");
}
else {
if (!inbody && !strncmp("From ", line, 5))
continue;
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 |= MDIR_F_READ;
else
mbox->msg[number].flags |= MDIR_F_TOP;
close(mbox->fd);
return(TRUE);
}
static void
moremessages(struct mbox *mbox)
{
mbox->msg = xrealloc(mbox->msg,
(mbox->max += MAXINCR) * sizeof(struct message));
}
static int
maildir_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 & MDIR_F_DELETE)
continue;
sendline(SEND_BUF, "%d %s", i + 1, mbox->msg[i].uidl);
}
return(TRUE);
}
static int
maildir_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 & MDIR_F_DELETE)
continue;
sendline(SEND_BUF, "%d %d", i + 1, mbox->msg[i].bytes);
}
return(TRUE);
}
static int
maildir_is_message(struct mbox *mbox, int number)
{
if (number > mbox->count || number < 0) {
message(NOSUCH);
return(FALSE);
}
if (mbox->msg[number].flags & MDIR_F_DELETE) {
message(ALREADYDELETED);
return(FALSE);
}
return(TRUE);
}
int
maildir_mbox_op(struct connection *cxn, enum cmd cmd, ...)
{
static struct mbox mbox;
va_list ap;
int arg1, arg2, i, bytes, msgs;
static int last;
switch (cmd) {
case SESSION_START:
flags = cxn->flags;
if (maildir_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);
break;
case SESSION_END:
maildir_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 (!maildir_is_message(&mbox, arg1 - 1))
return(FALSE);
mbox.msg[arg1 - 1].flags |= MDIR_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 (!maildir_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %d", arg1,
mbox.msg[arg1 - 1].bytes);
}
else {
maildir_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();
maildir_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 (last < arg1)
last = arg1;
if (!maildir_is_message(&mbox, arg1 - 1))
return(FALSE);
if (last < arg1)
last = arg1;
maildir_get_message_lines(&mbox, arg1 - 1, -1);
break;
case RSET:
for (i = 0; i <= mbox.count; i++)
mbox.msg[i].flags &= !(MDIR_F_DELETE | MDIR_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 & MDIR_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 (!maildir_is_message(&mbox, arg1 - 1))
return(FALSE);
maildir_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 (!maildir_is_message(&mbox, arg1 - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %s", arg1,
mbox.msg[arg1 - 1].uidl);
}
else {
maildir_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_SIZE: /* not reached */
case BUL_MSGS:
case APOP:
case AUTH:
case PASS:
case USER:
case TIMEDOUT:
case INVALCMD:
break;
}
return(FALSE);
}
syntax highlighted by Code2HTML, v. 0.9.1