/* * Copyright (c) 2014 Sunil Nimmagadda * * 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 #include #include #include #include #include #include #include #include #include #include #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); }