/* * 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 #include #include #include #include #ifdef SOLARIS #include "/usr/ucbinclude/fcntl.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }