summaryrefslogtreecommitdiff
path: root/mbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'mbox.c')
-rw-r--r--mbox.c206
1 files changed, 206 insertions, 0 deletions
diff --git a/mbox.c b/mbox.c
new file mode 100644
index 0000000..83b3d80
--- /dev/null
+++ b/mbox.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "pop3d.h"
+
+static int init(struct mdrop *, size_t *, size_t *);
+static int retr(struct mdrop *, unsigned int, size_t *, size_t *);
+static int update(struct mdrop *);
+
+struct m_backend m_backend_mbox = {
+ init,
+ retr,
+ update
+};
+
+/*
+ * Parse mbox calculating each message's offset, size and hash.
+ * A message is identified by "From " at the start of a line.
+ * Returns 0 on success with nmsgs and sz populated.
+ */
+int
+init(struct mdrop *m, size_t *nmsgs, size_t *sz)
+{
+ SHA1_CTX ctx;
+ FILE *fp;
+ struct msg *msg = NULL;
+ size_t i, len;
+ long offset;
+ char *line;
+
+ *nmsgs = 0;
+ *sz = 0;
+ if (flock(m->fd, LOCK_EX|LOCK_NB) == -1) {
+ switch (errno) {
+ case EWOULDBLOCK:
+ logit(LOG_INFO, "mbox: locked by other process");
+ return (-1);
+ default:
+ fatal("flock(LOCK_EX)");
+ }
+ }
+
+ if ((fp = fdopen(dup(m->fd), "r+")) == NULL) {
+ logit(LOG_INFO, "mbox: fdopen failed");
+ return (-1);
+ }
+
+ SIMPLEQ_INIT(&m->e.q_msgs);
+ offset = ftell(fp);
+ while ((line = fgetln(fp, &len))) {
+ if ((len > 5) && strncmp("From ", line, 5) == 0) {
+ if (msg)
+ SHA1End(&ctx, msg->hash);
+
+ msg = xcalloc(1, sizeof(*msg), "init");
+ SHA1Init(&ctx);
+ msg->u.offset = offset;
+ m->nmsgs += 1;
+ SIMPLEQ_INSERT_TAIL(&m->e.q_msgs, msg, e.q_entry);
+ } else {
+ if (msg == NULL)
+ fatalx("mbox corrupted: no \"From \" line");
+
+ msg->sz += len;
+ msg->nlines += 1;
+ SHA1Update(&ctx, (u_int8_t *)line, len);
+ offset = ftell(fp);
+ }
+ }
+
+ if (msg)
+ SHA1End(&ctx, msg->hash);
+
+ /* allocate space for nmsgs of struct msg pointers */
+ m->msgs_index = xcalloc(m->nmsgs, sizeof(msg), "make_index");
+ i = 0;
+ SIMPLEQ_FOREACH(msg, &m->e.q_msgs, e.q_entry) {
+ m->msgs_index[i++] = msg;
+ /* calculate mbox size by counting newline as 2 (CRLF) */
+ m->sz += msg->sz + msg->nlines;
+ }
+
+ *nmsgs = m->nmsgs;
+ *sz = m->sz;
+ fclose(fp);
+ return (0);
+}
+
+static int
+retr(struct mdrop *m, unsigned int idx, size_t *nlines, size_t *offset)
+{
+ if (m->msgs_index[idx]->flags & F_DELE)
+ return (-1);
+
+ *offset = m->msgs_index[idx]->u.offset;
+ *nlines = m->msgs_index[idx]->nlines;
+ return (dup(m->fd)); /* imsg closes sender's fd */
+}
+
+/*
+ * No resource management as this process is blown away
+ * upon success or error.
+ */
+static int
+update(struct mdrop *m)
+{
+ struct msg *cur;
+ size_t i, j = 0, len, nlines;
+ char buf[MAXBSIZE], fn[22], *line;
+ FILE *tmp_fp, *m_fp;
+ mode_t old_mask;
+ int tmp_fd;
+
+ for (i = 0; i < m->nmsgs; i++)
+ if (m->msgs_index[i]->flags & F_DELE)
+ j += 1;
+
+ if ((m_fp = fdopen(dup(m->fd), "r+")) == NULL) {
+ logit(LOG_INFO, "mbox: fdopen failed");
+ return (-1);
+ }
+
+ if (j == 0)
+ return (0);
+ else if (j == m->nmsgs) {
+ if (ftruncate(fileno(m_fp), 0) == -1) {
+ logit(LOG_CRIT, "update: ftruncate failed");
+ return (1);
+ }
+
+ return (0);
+ }
+
+ strlcpy(fn, "/tmp/pop3d.XXXXXXXXXX", sizeof(fn));
+ old_mask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if ((tmp_fd = mkstemp(fn)) == -1 ||
+ (tmp_fp = fdopen(tmp_fd, "r+")) == NULL) {
+ logit(LOG_CRIT, "mbox: mkstemp failed");
+ return (1);
+ }
+
+ umask(old_mask);
+ for (i = 0; i < m->nmsgs; i++) {
+ cur = m->msgs_index[i];
+ if (cur->flags & F_DELE)
+ continue;
+
+ if (fseek(m_fp, cur->u.offset, SEEK_SET) == -1) {
+ logit(LOG_CRIT, "update: fseek failed");
+ return (1);
+ }
+ /*
+ * "From " line isn't counted in nlines but offset starts
+ * there, adjust nlines here
+ */
+ nlines = m->msgs_index[i]->nlines + 1;
+ while (nlines--) {
+ if ((line = fgetln(m_fp, &len)))
+ if (fwrite(line, len, 1, tmp_fp) != 1)
+ fatalx("update: short write");
+ }
+ }
+
+ fflush(tmp_fp);
+ rewind(m_fp);
+ rewind(tmp_fp);
+ while (!feof(tmp_fp)) {
+ fread(buf, sizeof(buf), 1, tmp_fp);
+ if (fwrite(buf, sizeof(buf), 1, m_fp) != 1)
+ fatalx("update: short write");
+ }
+
+ fflush(m_fp);
+ if (ftruncate(fileno(m_fp), ftello(tmp_fp)) == -1)
+ fatal("update: failed to truncate");
+
+ fclose(m_fp);
+ return (0);
+}
+