/* * 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 #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" 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); }