/*
* 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_bulletin.c,v 1.10 2003/01/24 09:35:19 ianf Exp $
*/
#include <sys/types.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"
/*
* Global Variables
*/
extern struct config config;
/* Bulletins */
#define BUL_F_OK 0x0001
#define BUL_F_NOSUCH 0x0002
#define BUL_F_DELETE 0x0004
#define BUL_F_EXPIRE 0x0008
#define BUL_F_REMOVE 0x0010
#define BUL_F_READ 0x0020
#define BUL_F_TOP 0x0040
#define BUL_F_STAT 0x0080
struct message {
char *path;
char uidl[33];
time_t d_time;
size_t bytes;
int flags;
int fd;
};
struct mbox {
size_t bytes;
int count;
int max;
struct message *msg;
};
static int have_we_seen(struct connection *cxn, char *dir, char *bulletin);
static void moremessages(struct mbox *mbox);
static int bulletin_open(struct mbox *mbox, struct connection *cxn);
static void bulletin_close(struct mbox *mbox, struct connection *cxn);
static int bulletin_is_message(struct mbox *mbox, int number);
static int bulletin_list_all(struct mbox *mbox, int offset);
static int bulletin_uidl_all(struct mbox *mbox, int offset);
static int bulletin_get_message_lines(struct connection *cxn,
struct mbox *mbox, int number, int lines);
static int
have_we_seen(struct connection *cxn, char *dir, char *bulletin)
{
DB *dp;
DBT key, data;
char *path;
path = xmalloc(strlen(dir) + strlen(bulletin) + 9);
strcpy(path, dir);
strcat(path, "/.db/");
strcat(path, bulletin);
strcat(path, ".db");
seteuid(cxn->uid);
if (!(dp = dbopen(path, O_RDONLY|O_EXLOCK,
S_IRUSR|S_IWUSR, DB_HASH, NULL))) {
free(path);
seteuid(cxn->euid);
return(FALSE);
}
key.data = (u_char *)cxn->auth_string;
key.size = strlen(key.data);
if (!(dp->get)(dp, &key, &data, 0)) {
(dp->close)(dp);
free(path);
seteuid(cxn->euid);
return(TRUE);
}
(dp->close)(dp);
free(path);
seteuid(cxn->euid);
return(FALSE);
}
static void
moremessages(struct mbox *mbox)
{
mbox->msg = xrealloc(mbox->msg,
(mbox->max += MAXINCR) * sizeof(struct message));
}
static int
bulletin_open(struct mbox *mbox, struct connection *cxn)
{
MD5_CTX context;
DIR *dirp = NULL;
struct dirent *dp;
struct stat stat_s;
char *path[2], uidldat[MAXBUFLEN + 1];
unsigned char digest[16];
int pathlen[2], uidlen, i;
mbox->count = -1;
mbox->max = -1;
mbox->bytes = 0;
mbox->msg = NULL;
i = 1;
if (cxn->realm) {
pathlen[0] = strlen(cxn->bulletinpath) + strlen(cxn->realm) + 2;
path[0] = xmalloc(pathlen[0]);
strcpy(path[0], cxn->bulletinpath);
strcat(path[0], "/");
strcat(path[0], cxn->realm);
i = 0;
}
path[1] = cxn->bulletinpath;
pathlen[1] = strlen(path[1]);
for (;i < 2; i++) {
if (!(dirp = opendir(path[i]))) {
if (cxn->realm)
free(path[0]);
return(FALSE);
}
while ((dp = readdir(dirp)) != NULL) {
if (have_we_seen(cxn, path[i], dp->d_name))
continue;
if (++mbox->count > mbox->max)
moremessages(mbox);
mbox->msg[mbox->count].fd = -1;
mbox->msg[mbox->count].path =
xmalloc(strlen(dp->d_name) + pathlen[i] + 3);
strcpy(mbox->msg[mbox->count].path, path[i]);
strcat(mbox->msg[mbox->count].path, "/");
strcat(mbox->msg[mbox->count].path, dp->d_name);
stat(mbox->msg[mbox->count].path, &stat_s);
if ((stat_s.st_mode & S_IFMT) == S_IFDIR) {
free(mbox->msg[mbox->count--].path);
continue;
}
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;
uidlen = snprintf(uidldat, MAXBUFLEN, "%d%s%d",
mbox->msg[mbox->count].d_time,
mbox->msg[mbox->count].path,
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 (cxn->realm)
free(path[0]);
return(TRUE);
}
static void
bulletin_close(struct mbox *mbox, struct connection *cxn)
{
DB *dp;
DBT key, data;
char *path, *p, *q;
int i;
time_t tm;
path = NULL;
for (i = 0; i <= mbox->count; i++) {
if (!(mbox->msg[i].flags & BUL_F_DELETE))
continue;
/* Split the path into the directory and filename */
for (p = strchr(mbox->msg[i].path, '/');
p && (q = strchr(p + 1, '/')); p = q);
if (p == NULL)
continue;
*p++ = '\0';
path = xrealloc(path, strlen(mbox->msg[i].path) +
strlen(p) + 9);
strcpy(path, mbox->msg[i].path);
strcat(path, "/.db/");
strcat(path, p);
strcat(path, ".db");
seteuid(cxn->uid);
if (!(dp = dbopen(path, O_RDWR|O_CREAT|O_EXLOCK,
S_IRUSR|S_IWUSR, DB_HASH, NULL))) {
free(path);
seteuid(cxn->euid);
return;
}
key.data = (u_char *)cxn->auth_string;
key.size = strlen(key.data);
tm = time(NULL);
data.data = (u_char *)&tm;
data.size = sizeof(tm);;
(dp->put)(dp, &key, &data, 0);
(dp->close)(dp);
seteuid(cxn->euid);
}
free(path);
}
static int
bulletin_get_message_lines(struct connection *cxn, struct mbox *mbox,
int number, int lines)
{
char buffer[MAXBUFLEN], *p;
int fd, inbody, count, bytes, len, buffleft;
char *line, *offset;
p = buffer;
inbody = FALSE;
count = 0;
bytes = mbox->msg[number].bytes;
seteuid(cxn->uid);
fd = openlock(mbox->msg[number].path, O_RDWR|O_NONBLOCK);
seteuid(cxn->euid);
if (fd < 0) {
sendline(SEND_FLUSH, "-ERR (bulletin) error opening message "
"'%s': %s", mbox->msg[number].path, 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(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 |= BUL_F_READ;
else
mbox->msg[number].flags |= BUL_F_TOP;
close(fd);
return(TRUE);
}
static int
bulletin_uidl_all(struct mbox *mbox, int offset)
{
int i;
for (i = 0; i <= mbox->count; i++) {
if (mbox->msg[i].flags & BUL_F_DELETE)
continue;
sendline(SEND_BUF, "%d %s", offset + i + 1, mbox->msg[i].uidl);
}
return(TRUE);
}
static int
bulletin_list_all(struct mbox *mbox, int offset)
{
int i;
for (i = 0; i <= mbox->count; i++) {
if (mbox->msg[i].flags & BUL_F_DELETE)
continue;
sendline(SEND_BUF, "%d %d", offset + i + 1, mbox->msg[i].bytes);
}
return(TRUE);
}
static int
bulletin_is_message(struct mbox *mbox, int number)
{
if (number > mbox->count || number < 0) {
message(NOSUCH);
return(FALSE);
}
if (mbox->msg[number].flags & BUL_F_DELETE) {
message(ALREADYDELETED);
return(FALSE);
}
return(TRUE);
}
int
bulletin_mbox_op(struct connection *cxn, enum cmd cmd, ...)
{
static struct mbox mbox;
va_list ap;
int arg1, arg2, offset, i;
if (cxn->bulletinpath == NULL)
return(0);
switch (cmd) {
case SESSION_START:
bulletin_open(&mbox, cxn);
break;
case SESSION_END:
bulletin_close(&mbox, cxn);
return(TRUE);
case DELE:
va_start(ap, cmd);
offset = va_arg(ap, int);
arg1 = va_arg(ap, int);
va_end(ap);
if (!bulletin_is_message(&mbox, arg1 - offset - 1))
return(FALSE);
mbox.msg[arg1 - offset - 1].flags |= BUL_F_DELETE;
sendline(SEND_FLUSH, "+OK message deleted");
break;
case LIST:
va_start(ap, cmd);
offset = va_arg(ap, int);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 >= 0) {
if (!bulletin_is_message(&mbox, arg1 - offset - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %d", arg1,
mbox.msg[arg1 - offset - 1].bytes);
}
else {
bulletin_list_all(&mbox, offset);
}
break;
case QUIT:
bulletin_mbox_op(cxn, SESSION_END);
break;
case RETR:
va_start(ap, cmd);
offset = va_arg(ap, int);
arg1 = va_arg(ap, int);
va_end(ap);
if (!bulletin_is_message(&mbox, arg1 - offset - 1))
return(FALSE);
bulletin_get_message_lines(cxn, &mbox, arg1 - offset - 1, -1);
break;
case RSET:
for (i = 0; i <= mbox.count; i++)
mbox.msg[i].flags &= !(BUL_F_DELETE | BUL_F_READ);
break;
case TOP:
va_start(ap, cmd);
offset = va_arg(ap, int);
arg1 = va_arg(ap, int);
arg2 = va_arg(ap, int);
va_end(ap);
if (!bulletin_is_message(&mbox, arg1 - offset - 1))
return(FALSE);
bulletin_get_message_lines(cxn, &mbox, arg1 - offset - 1, arg2);
break;
case UIDL:
va_start(ap, cmd);
offset = va_arg(ap, int);
arg1 = va_arg(ap, int);
va_end(ap);
if (arg1 >= 0) {
if (!bulletin_is_message(&mbox, arg1 - offset - 1))
return(FALSE);
sendline(SEND_FLUSH, "+OK %d %s", arg1,
mbox.msg[arg1 -offset - 1].uidl);
}
else
bulletin_uidl_all(&mbox, offset);
break;
case BUL_MSGS:
return(mbox.count + 1);
case BUL_SIZE:
return(mbox.bytes);
case NOOP:
/* noop not required for meta mailbox bulletin */
case LAST:
/* last not required for meta mailbox bulletin */
case STAT:
/* stat not required for meta mailbox bulletin */
case APOP: /* not reached */
case AUTH:
case PASS:
case USER:
case INVALCMD:
case TIMEDOUT:
break;
}
return(TRUE);
}
syntax highlighted by Code2HTML, v. 0.9.1