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