/*
 * 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