summaryrefslogtreecommitdiff
path: root/usr.sbin/popa3d/mailbox.c
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>2001-08-19 13:05:58 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>2001-08-19 13:05:58 +0000
commita6a38350f779ddf3a6febf67dda7714fe368e492 (patch)
treeb8ffbc248c20776618b1d6aa93ec16abb19e78d1 /usr.sbin/popa3d/mailbox.c
parent580e400b41d341c2ad1bfe153d5ca6553351703a (diff)
libexec is the wrong place for popa3d, since it can be started WITHOUT inetd
Diffstat (limited to 'usr.sbin/popa3d/mailbox.c')
-rw-r--r--usr.sbin/popa3d/mailbox.c453
1 files changed, 453 insertions, 0 deletions
diff --git a/usr.sbin/popa3d/mailbox.c b/usr.sbin/popa3d/mailbox.c
new file mode 100644
index 00000000000..3316ed26b4a
--- /dev/null
+++ b/usr.sbin/popa3d/mailbox.c
@@ -0,0 +1,453 @@
+/* $OpenBSD: mailbox.c,v 1.1 2001/08/19 13:05:57 deraadt Exp $ */
+
+/*
+ * Mailbox access.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <errno.h>
+
+#include <md5.h>
+
+#include "misc.h"
+#include "params.h"
+#include "protocol.h"
+#include "database.h"
+
+static int mailbox_fd; /* fd for the mailbox, or -1 */
+static time_t mailbox_mtime; /* mtime, as of the last check */
+static long mailbox_size; /* Its original size */
+
+static struct db_message *cmp;
+
+/*
+ * If a message has changed since the database was filled in, then we
+ * consider the database stale. This is called for every message when
+ * the mailbox is being re-parsed (because of its mtime change).
+ */
+static int db_compare(struct db_message *msg)
+{
+ if (!cmp) return 1;
+
+ if (msg->raw_size != cmp->raw_size || msg->size != cmp->size ||
+ memcmp(msg->hash, cmp->hash, sizeof(msg->hash))) {
+ db.flags |= DB_STALE;
+ return 1;
+ }
+
+ cmp = cmp->next;
+
+ return 0;
+}
+
+/*
+ * Checks if the buffer pointed to by s1, of n1 chars, starts with the
+ * string s2.
+ */
+static int linecmp(char *s1, char *s2, int n1)
+{
+ int n2;
+
+ if (s1[0] != s2[0]) return 1;
+ if (n1 < (n2 = strlen(s2))) return 1;
+ return memcmp(s1, s2, n2);
+}
+
+/*
+ * The mailbox parsing routine: first called to fill the database in,
+ * then to check if the database is still up to date. We implement a
+ * state machine at the line fragment level (that is, full or partial
+ * lines). This is faster than dealing with individual characters (we
+ * leave that job for libc), and doesn't require ever loading entire
+ * lines into memory.
+ */
+static int mailbox_parse(int init)
+{
+ struct stat stat; /* File information */
+ struct db_message msg; /* Message being parsed */
+ MD5_CTX hash; /* Its hash being computed */
+ int (*db_op)(struct db_message *msg); /* db_add or db_compare */
+ char *file_buffer, *line_buffer; /* Our internal buffers */
+ long file_offset, line_offset, offset; /* Their offsets in the file */
+ char *current, *next, *line; /* Line pointers */
+ int block, saved, extra, length; /* Internal block sizes */
+ int done, start, end; /* Various boolean flags: */
+ int blank, header, body, fixed; /* the state information */
+
+ if (fstat(mailbox_fd, &stat)) return 1;
+
+ if (init) {
+/* Prepare for the database initialization */
+ if (!S_ISREG(stat.st_mode)) return 1;
+ mailbox_mtime = stat.st_mtime;
+ mailbox_size = stat.st_size;
+ if (mailbox_size < 0) return 1;
+ if (!mailbox_size) return 0;
+ if (mailbox_size > MAX_MAILBOX_BYTES) return 1;
+ db_op = db_add;
+ } else {
+/* Prepare for checking against the database */
+ if (mailbox_mtime == stat.st_mtime) return 0;
+ if (!mailbox_size) return 0;
+ if (mailbox_size > (long)stat.st_size) {
+ db.flags |= DB_STALE;
+ return 1;
+ }
+ if (lseek(mailbox_fd, 0, SEEK_SET) < 0) return 1;
+ db_op = db_compare; cmp = db.head;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ MD5Init(&hash);
+
+ file_buffer = malloc(FILE_BUFFER_SIZE + LINE_BUFFER_SIZE);
+ if (!file_buffer) return 1;
+ line_buffer = &file_buffer[FILE_BUFFER_SIZE];
+
+ file_offset = 0; line_offset = 0; offset = 0; /* Start at 0, with */
+ current = file_buffer; block = 0; saved = 0; /* empty buffers */
+
+ done = 0; /* Haven't reached EOF or the original size yet */
+ end = 1; /* Assume we've just seen a LF: parse a new line */
+ blank = 1; /* Assume we've seen a blank line: look for "From " */
+ header = 0; /* Not in message headers, */
+ body = 0; /* and not in message body */
+ fixed = 0; /* Not in a "fixed" part of a message */
+
+/*
+ * The main loop. Its first part extracts the line fragments, while the
+ * second one manages the state flags and performs whatever is required
+ * based on the state. Unfortunately, splitting this into two functions
+ * didn't seem to simplify the code.
+ */
+ do {
+/*
+ * Part 1.
+ * The line fragment extraction.
+ */
+
+/* Look for the next LF in the file buffer */
+ if ((next = memchr(current, '\n', block))) {
+/* Found it: get the length of this piece, and check for buffered data */
+ length = ++next - current;
+ if (saved) {
+/* Have this line's beginning in the line buffer: combine them */
+ extra = LINE_BUFFER_SIZE - saved;
+ if (extra > length) extra = length;
+ memcpy(&line_buffer[saved], current, extra);
+ current += extra; block -= extra;
+ length = saved + extra;
+ line = line_buffer;
+ offset = line_offset;
+ start = end; end = current == next;
+ saved = 0;
+ } else {
+/* Nothing in the line buffer: just process what we've got now */
+ line = current;
+ offset = file_offset - block;
+ start = end; end = 1;
+ current = next; block -= length;
+ }
+ } else {
+/* No more LF's in the file buffer */
+ if (saved || block <= LINE_BUFFER_SIZE) {
+/* Have this line's beginning in the line buffer: combine them */
+/* Not enough data to process right now: buffer it */
+ extra = LINE_BUFFER_SIZE - saved;
+ if (extra > block) extra = block;
+ if (!saved) line_offset = file_offset - block;
+ memcpy(&line_buffer[saved], current, extra);
+ current += extra; block -= extra;
+ saved += extra;
+ length = saved;
+ line = line_buffer;
+ offset = line_offset;
+ } else {
+/* Nothing in the line buffer and we've got enough data: just process it */
+ length = block - 1;
+ line = current;
+ offset = file_offset - block;
+ current += length;
+ block = 1;
+ }
+ if (!block) {
+/* We've emptied the file buffer: fetch some more data */
+ current = file_buffer;
+ if (init)
+ block = FILE_BUFFER_SIZE;
+ else {
+ block = mailbox_size - file_offset;
+ if (block > FILE_BUFFER_SIZE)
+ block = FILE_BUFFER_SIZE;
+ }
+ block = read(mailbox_fd, file_buffer, block);
+ if (block < 0) break;
+ file_offset += block;
+ if (block > 0 && saved < LINE_BUFFER_SIZE)
+ continue;
+ if (!saved) {
+/* Nothing in the line buffer, and read(2) returned 0: we're done */
+ offset = file_offset;
+ done = 1;
+ break;
+ }
+ }
+ start = end; end = !block;
+ saved = 0;
+ }
+
+/*
+ * Part 2.
+ * The following variables are set when we get here:
+ * -- line the line fragment, not NUL terminated;
+ * -- length its length;
+ * -- offset its offset in the file;
+ * -- start whether it's at the start of the line;
+ * -- end whether it's at the end of the line
+ * (all four combinations of "start" and "end" are possible).
+ */
+
+/* Check for a new message if we've just seen a blank line */
+ if (blank && start)
+ if (!linecmp(line, "From ", length)) {
+/* Process the previous one first, if exists */
+ if (offset) {
+ if (!header && !body) break;
+ msg.raw_size = offset - msg.raw_offset;
+ msg.data_size = offset - msg.data_offset;
+ MD5Final(msg.hash, &hash);
+ if (db_op(&msg)) break;
+ }
+/* Now prepare for parsing the new one */
+ msg.raw_offset = offset;
+ msg.data_offset = 0;
+ MD5Init(&hash);
+ header = 1; body = 0;
+ continue;
+ }
+
+/* Memorize file offset of the message data (the line next to "From ") */
+ if (header && start && !msg.data_offset) {
+ msg.data_offset = offset;
+ msg.data_size = 0;
+ msg.size = 0;
+ }
+
+/* Count this fragment, with LF's as CRLF, into the message size */
+ if (msg.data_offset)
+ msg.size += length + end;
+
+/* If we see LF at start of line, then this is a blank line :-) */
+ blank = start && line[0] == '\n';
+
+/* Blank line in headers means start of the message body */
+ if (header && blank) {
+ header = 0; body = 1;
+ }
+
+/* Some header lines are known to remain fixed over MUA runs */
+ if (header && start)
+ if (!linecmp(line, "Received:", length) ||
+ !linecmp(line, "Date:", length) ||
+ !linecmp(line, "Message-Id:", length) ||
+ !linecmp(line, "Subject:", length))
+ fixed = 1;
+
+/* We can hash all fragments of those lines (until "end"), for UIDL */
+ if (fixed) {
+ MD5Update(&hash, line, length);
+ if (end) fixed = 0;
+ }
+ } while (1);
+
+ free(file_buffer);
+
+ if (done) {
+/* Process the last message */
+ if (offset != mailbox_size) return 1;
+ if (!header && !body) return 1;
+ msg.raw_size = offset - msg.raw_offset;
+ msg.data_size = offset - msg.data_offset;
+ MD5Final(msg.hash, &hash);
+ if (db_op(&msg)) return 1;
+
+/* Everything went well, update our timestamp if we were checking */
+ if (!init) mailbox_mtime = stat.st_mtime;
+ }
+
+ return !done;
+}
+
+int mailbox_open(char *spool, char *mailbox)
+{
+ char *pathname;
+ struct stat stat;
+ int result;
+
+ mailbox_fd = -1;
+
+ pathname = malloc(strlen(spool) + strlen(mailbox) + 2);
+ if (!pathname) return 1;
+ sprintf(pathname, "%s/%s", spool, mailbox);
+
+ if (lstat(pathname, &stat)) {
+ free(pathname);
+ return errno != ENOENT;
+ }
+
+ if (!stat.st_size) {
+ free(pathname);
+ return 0;
+ }
+
+ mailbox_fd = open(pathname, O_RDWR);
+
+ free(pathname);
+
+ if (mailbox_fd < 0)
+ return errno != ENOENT;
+
+ if (lock_fd(mailbox_fd, 1)) return 1;
+
+ result = mailbox_parse(1);
+
+ if (!result && time(NULL) == mailbox_mtime)
+ if (sleep_select(1, 0)) result = 1;
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+static int mailbox_changed(void)
+{
+ struct stat stat;
+ int result;
+
+ if (fstat(mailbox_fd, &stat)) return 1;
+ if (mailbox_mtime == stat.st_mtime) return 0;
+
+ if (lock_fd(mailbox_fd, 1)) return 1;
+
+ result = mailbox_parse(0);
+
+ if (!result && time(NULL) == mailbox_mtime)
+ if (sleep_select(1, 0)) result = 1;
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+int mailbox_get(struct db_message *msg, int lines)
+{
+ if (mailbox_changed()) return 1;
+
+ if (lseek(mailbox_fd, msg->data_offset, SEEK_SET) < 0) return 1;
+ if (pop_reply_multiline(mailbox_fd, msg->data_size, lines)) return 1;
+
+ if (mailbox_changed()) return 1;
+
+ return pop_reply_terminate();
+}
+
+static int mailbox_write(char *buffer)
+{
+ struct db_message *msg;
+ long old, new;
+ int block;
+
+ msg = db.head;
+ old = new = 0;
+ do {
+ if (msg->flags & MSG_DELETED) continue;
+ old = msg->raw_offset;
+
+ if (old == new) {
+ old = (new += msg->raw_size);
+ continue;
+ }
+
+ while ((block = msg->raw_size - (old - msg->raw_offset))) {
+ if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
+ if (block > FILE_BUFFER_SIZE) block = FILE_BUFFER_SIZE;
+ block = read(mailbox_fd, buffer, block);
+ if (!block && old == mailbox_size) break;
+ if (block <= 0) return 1;
+
+ if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
+ if (write_loop(mailbox_fd, buffer, block) != block)
+ return 1;
+
+ old += block; new += block;
+ }
+ } while ((msg = msg->next));
+
+ old = mailbox_size;
+ while (1) {
+ if (lseek(mailbox_fd, old, SEEK_SET) < 0) return 1;
+ block = read(mailbox_fd, buffer, FILE_BUFFER_SIZE);
+ if (!block) break;
+ if (block < 0) return 1;
+
+ if (lseek(mailbox_fd, new, SEEK_SET) < 0) return 1;
+ if (write(mailbox_fd, buffer, block) != block) return 1;
+
+ old += block; new += block;
+ }
+
+ if (ftruncate(mailbox_fd, new)) return 1;
+
+ return fsync(mailbox_fd);
+}
+
+static int mailbox_write_blocked(void)
+{
+ sigset_t blocked_set, old_set;
+ char *buffer;
+ int result;
+
+ if (sigfillset(&blocked_set)) return 1;
+ if (sigprocmask(SIG_BLOCK, &blocked_set, &old_set)) return 1;
+
+ if ((buffer = malloc(FILE_BUFFER_SIZE))) {
+ result = mailbox_write(buffer);
+ free(buffer);
+ } else
+ result = 1;
+
+ if (sigprocmask(SIG_SETMASK, &old_set, NULL)) return 1;
+
+ return result;
+}
+
+int mailbox_update(void)
+{
+ int result;
+
+ if (mailbox_fd < 0 || !(db.flags & DB_DIRTY)) return 0;
+
+ if (lock_fd(mailbox_fd, 0)) return 1;
+
+ if (!(result = mailbox_parse(0)))
+ result = mailbox_write_blocked();
+
+ if (unlock_fd(mailbox_fd)) return 1;
+
+ return result;
+}
+
+int mailbox_close(void)
+{
+ if (mailbox_fd < 0) return 0;
+
+ return close(mailbox_fd);
+}