]> git.ipfire.org Git - people/ms/dma.git/blobdiff - spool.c
Merge pull request #34 from mtremer/better-authentication
[people/ms/dma.git] / spool.c
diff --git a/spool.c b/spool.c
index 77cd6992e7c045b603329e6ff190e84ae996d8ef..e9c9c4355ab45e62c26b0582aa81228c9653fa32 100644 (file)
--- a/spool.c
+++ b/spool.c
@@ -1,8 +1,9 @@
 /*
+ * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
  *
  * This code is derived from software contributed to The DragonFly Project
- * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>.
+ * by Simon Schubert <2@0x2c.org>.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * 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.
- *
- * $DragonFly$
  */
 
+#include "dfcompat.h"
+
+#include <sys/file.h>
 #include <sys/stat.h>
 
+#include <ctype.h>
 #include <dirent.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <unistd.h>
+#include <syslog.h>
 
 #include "dma.h"
 
  * Spool file format:
  *
  * 'Q'id files (queue):
- *   id envelope-to
+ *   Organized like an RFC822 header, field: value.  Ignores unknown fields.
+ *   ID: id
+ *   Sender: envelope-from
+ *   Recipient: envelope-to
  *
  * 'M'id files (data):
- *   envelope-from
  *   mail data
  *
  * Each queue file needs to have a corresponding data file.
  * One data file might be shared by linking it several times.
  *
- * Queue ids are unique, formed from the inode of the spool file
+ * Queue ids are unique, formed from the inode of the data file
  * and a unique identifier.
  */
 
 int
-newspoolf(struct queue *queue, const char *sender)
+newspoolf(struct queue *queue)
 {
-       char line[1000];        /* by RFC2822 */
        char fn[PATH_MAX+1];
+       struct stat st;
        struct stritem *t;
-       struct qitem *it;
-       FILE *mailf;
-       off_t hdrlen;
-       int error;
+       int fd;
 
-       if (snprintf(fn, sizeof(fn), "%s/%s", config->spooldir, "tmp_XXXXXXXXXX") <= 0)
+       if (snprintf(fn, sizeof(fn), "%s/%s", config.spooldir, "tmp_XXXXXXXXXX") <= 0)
                return (-1);
 
-       queue->mailfd = mkstemp(fn);
-       if (queue->mailfd < 0)
+       fd = mkstemp(fn);
+       if (fd < 0)
                return (-1);
-       if (flock(queue->mailfd, LOCK_EX) == -1)
-               return (-1);
-
+       /* XXX group rights */
+       if (fchmod(fd, 0660) < 0)
+               goto fail;
+       if (flock(fd, LOCK_EX) == -1)
+               goto fail;
        queue->tmpf = strdup(fn);
        if (queue->tmpf == NULL)
                goto fail;
 
-       error = snprintf(line, sizeof(line), "%s\n", sender);
-       if (error < 0 || (size_t)error >= sizeof(line)) {
-               errno = E2BIG;
+       /*
+        * Assign queue id
+        */
+       if (fstat(fd, &st) != 0)
                goto fail;
-       }
-       if (write(queue->mailfd, line, error) != error)
+       if (asprintf(&queue->id, "%"PRIxMAX, (uintmax_t)st.st_ino) < 0)
                goto fail;
 
-       hdrlen = lseek(queue->mailfd, 0, SEEK_CUR);
-
-       mailf = fdopen(queue->mailfd, "r+");
-       if (mailf == NULL)
+       queue->mailf = fdopen(fd, "r+");
+       if (queue->mailf == NULL)
                goto fail;
-       LIST_FOREACH(it, &queue->queue, next) {
-               it->mailf = mailf;
-               it->hdrlen = hdrlen;
-       }
 
        t = malloc(sizeof(*t));
        if (t != NULL) {
@@ -112,204 +111,331 @@ newspoolf(struct queue *queue, const char *sender)
        return (0);
 
 fail:
-       close(queue->mailfd);
+       if (queue->mailf != NULL)
+               fclose(queue->mailf);
+       close(fd);
        unlink(fn);
        return (-1);
 }
 
+static int
+writequeuef(struct qitem *it)
+{
+       int error;
+       int queuefd;
+
+       queuefd = open_locked(it->queuefn, O_CREAT|O_EXCL|O_RDWR, 0660);
+       if (queuefd == -1)
+               return (-1);
+       if (fchmod(queuefd, 0660) < 0)
+               return (-1);
+       it->queuef = fdopen(queuefd, "w+");
+       if (it->queuef == NULL)
+               return (-1);
+
+       error = fprintf(it->queuef,
+                       "ID: %s\n"
+                       "Sender: %s\n"
+                       "Recipient: %s\n",
+                        it->queueid,
+                        it->sender,
+                        it->addr);
+
+       if (error <= 0)
+               return (-1);
+
+       if (fflush(it->queuef) != 0 || fsync(fileno(it->queuef)) != 0)
+               return (-1);
+
+       return (0);
+}
+
+static struct qitem *
+readqueuef(struct queue *queue, char *queuefn)
+{
+       char line[1000];
+       struct queue itmqueue;
+       FILE *queuef = NULL;
+       char *s;
+       char *queueid = NULL, *sender = NULL, *addr = NULL;
+       struct qitem *it = NULL;
+
+       bzero(&itmqueue, sizeof(itmqueue));
+       LIST_INIT(&itmqueue.queue);
+
+       queuef = fopen(queuefn, "r");
+       if (queuef == NULL)
+               goto out;
+
+       while (!feof(queuef)) {
+               if (fgets(line, sizeof(line), queuef) == NULL || line[0] == 0)
+                       break;
+               line[strlen(line) - 1] = 0;     /* chop newline */
+
+               s = strchr(line, ':');
+               if (s == NULL)
+                       goto malformed;
+               *s = 0;
+
+               s++;
+               while (isspace(*s))
+                       s++;
+
+               s = strdup(s);
+               if (s == NULL)
+                       goto malformed;
+
+               if (strcmp(line, "ID") == 0) {
+                       queueid = s;
+               } else if (strcmp(line, "Sender") == 0) {
+                       sender = s;
+               } else if (strcmp(line, "Recipient") == 0) {
+                       addr = s;
+               } else {
+                       syslog(LOG_DEBUG, "ignoring unknown queue info `%s' in `%s'",
+                              line, queuefn);
+                       free(s);
+               }
+       }
+
+       if (queueid == NULL || sender == NULL || addr == NULL ||
+           *queueid == 0 || *addr == 0) {
+malformed:
+               errno = EINVAL;
+               syslog(LOG_ERR, "malformed queue file `%s'", queuefn);
+               goto out;
+       }
+
+       if (add_recp(&itmqueue, addr, 0) != 0)
+               goto out;
+
+       it = LIST_FIRST(&itmqueue.queue);
+       it->sender = sender; sender = NULL;
+       it->queueid = queueid; queueid = NULL;
+       it->queuefn = queuefn; queuefn = NULL;
+       LIST_INSERT_HEAD(&queue->queue, it, next);
+
+out:
+       if (sender != NULL)
+               free(sender);
+       if (queueid != NULL)
+               free(queueid);
+       if (addr != NULL)
+               free(addr);
+       if (queuef != NULL)
+               fclose(queuef);
+
+       return (it);
+}
+
 int
 linkspool(struct queue *queue)
 {
-       char line[1000];        /* by RFC2822 */
        struct stat st;
-       int error;
-       int queuefd;
        struct qitem *it;
 
-       /*
-        * Assign queue id to each dest.
-        */
-       if (fstat(queue->mailfd, &st) != 0)
-               return (-1);
-       queue->id = st.st_ino;
+       if (fflush(queue->mailf) != 0 || fsync(fileno(queue->mailf)) != 0)
+               goto delfiles;
 
-       /* XXX put this to a better place
-       syslog(LOG_INFO, "%"PRIxMAX": new mail from user=%s uid=%d envelope_from=<%s>",
-              queue->id, username, uid, sender);
-       */
+       syslog(LOG_INFO, "new mail from user=%s uid=%d envelope_from=<%s>",
+              username, getuid(), queue->sender);
 
        LIST_FOREACH(it, &queue->queue, next) {
-               if (asprintf(&it->queueid, "%"PRIxMAX".%"PRIxPTR, queue->id, (uintptr_t)it) <= 0)
+               if (asprintf(&it->queueid, "%s.%"PRIxPTR, queue->id, (uintptr_t)it) <= 0)
                        goto delfiles;
-               if (asprintf(&it->queuefn, "%s/Q%s", config->spooldir, it->queueid) <= 0)
+               if (asprintf(&it->queuefn, "%s/Q%s", config.spooldir, it->queueid) <= 0)
                        goto delfiles;
-               if (asprintf(&it->mailfn, "%s/M%s", config->spooldir, it->queueid) <= 0)
+               if (asprintf(&it->mailfn, "%s/M%s", config.spooldir, it->queueid) <= 0)
                        goto delfiles;
 
                /* Neither file may not exist yet */
                if (stat(it->queuefn, &st) == 0 || stat(it->mailfn, &st) == 0)
                        goto delfiles;
 
-               error = snprintf(line, sizeof(line), "%s %s\n", it->queueid, it->addr);
-               if (error < 0 || (size_t)error >= sizeof(line))
-                       goto delfiles;
-               queuefd = open_locked(it->queuefn, O_CREAT|O_EXCL|O_RDWR, 0600);
-               if (queuefd == -1)
+               if (writequeuef(it) != 0)
                        goto delfiles;
-               if (write(queuefd, line, error) != error) {
-                       close(queuefd);
-                       goto delfiles;
-               }
-               it->queuefd = queuefd;
 
                if (link(queue->tmpf, it->mailfn) != 0)
                        goto delfiles;
        }
 
-       /* XXX
-               syslog(LOG_INFO, "%"PRIxMAX": mail to=<%s> queued as %s",
-                      queue->id, it->addr, it->queueid);
-       */
+       LIST_FOREACH(it, &queue->queue, next) {
+               syslog(LOG_INFO, "mail to=<%s> queued as %s",
+                      it->addr, it->queueid);
+       }
 
        unlink(queue->tmpf);
        return (0);
 
 delfiles:
        LIST_FOREACH(it, &queue->queue, next) {
-               unlink(it->queuefn);
                unlink(it->mailfn);
+               unlink(it->queuefn);
        }
        return (-1);
 }
 
-void
-load_queue(struct queue *queue, int ignorelock)
+int
+load_queue(struct queue *queue)
 {
+       struct stat sb;
        struct qitem *it;
-       //struct queue queue, itmqueue;
-       struct queue itmqueue;
        DIR *spooldir;
        struct dirent *de;
-       char line[1000];
-       FILE *queuef;
-       FILE *mailf;
-       char *sender;
-       char *addr;
-       char *queueid;
        char *queuefn;
        char *mailfn;
-       off_t hdrlen;
-       int fd;
-       int locked;
 
+       bzero(queue, sizeof(*queue));
        LIST_INIT(&queue->queue);
 
-       spooldir = opendir(config->spooldir);
+       spooldir = opendir(config.spooldir);
        if (spooldir == NULL)
-               err(1, "reading queue");
+               err(EX_NOINPUT, "reading queue");
 
        while ((de = readdir(spooldir)) != NULL) {
-               fd = -1;
-               sender = NULL;
-               queuef = NULL;
-               mailf = NULL;
-               queueid = NULL;
                queuefn = NULL;
-               locked = 1;
-               LIST_INIT(&itmqueue.queue);
+               mailfn = NULL;
 
-               /* ignore temp files */
-               if (strncmp(de->d_name, "tmp_", 4) == 0 || de->d_type != DT_REG)
-                       continue;
+               /* ignore non-queue files */
                if (de->d_name[0] != 'Q')
                        continue;
-               if (asprintf(&queuefn, "%s/Q%s", config->spooldir, de->d_name + 1) < 0)
+               if (asprintf(&queuefn, "%s/Q%s", config.spooldir, de->d_name + 1) < 0)
                        goto fail;
-               if (asprintf(&mailfn, "%s/M%s", config->spooldir, de->d_name + 1) < 0)
+               if (asprintf(&mailfn, "%s/M%s", config.spooldir, de->d_name + 1) < 0)
                        goto fail;
 
-               fd = open_locked(queuefn, O_RDONLY|O_NONBLOCK);
-               if (ignorelock && fd < 0) {
-                       fd = open(queuefn, O_RDONLY);
-                       locked = 0;
-               }
-               if (fd < 0) {
-                       /* Ignore locked files */
-                       if (errno == EWOULDBLOCK)
-                               continue;
-                       goto skip_item;
-               }
-
-               mailf = fopen(mailfn, "r");
-               if (mailf == NULL)
-                       goto skip_item;
-               if (fgets(line, sizeof(line), mailf) == NULL || line[0] == 0)
+               /*
+                * Some file systems don't provide a de->d_type, so we have to
+                * do an explicit stat on the queue file.
+                * Move on if it turns out to be something else than a file.
+                */
+               if (stat(queuefn, &sb) != 0)
                        goto skip_item;
-               line[strlen(line) - 1] = 0;     /* chop newline */
-               sender = strdup(line);
-               if (sender == NULL)
+               if (!S_ISREG(sb.st_mode)) {
+                       errno = EINVAL;
                        goto skip_item;
+               }
 
-               hdrlen = ftell(mailf);
-
-               queuef = fdopen(fd, "r");
-               if (queuef == NULL)
+               if (stat(mailfn, &sb) != 0)
                        goto skip_item;
 
-               if (fgets(line, sizeof(line), queuef) == NULL || line[0] == 0)
+               it = readqueuef(queue, queuefn);
+               if (it == NULL)
                        goto skip_item;
-               line[strlen(line) - 1] = 0;
-               queueid = strdup(line);
-               if (queueid == NULL)
-                       goto skip_item;
-               addr = strchr(queueid, ' ');
-               if (addr == NULL)
-                       goto skip_item;
-               *addr++ = 0;
 
-               if (add_recp(&itmqueue, addr, sender, 0) != 0)
-                       goto skip_item;
-               it = LIST_FIRST(&itmqueue.queue);
-               it->queuefd = fd;
-               it->mailf = mailf;
-               it->queueid = queueid;
-               it->queuefn = queuefn;
                it->mailfn = mailfn;
-               it->hdrlen = hdrlen;
-               it->locked = locked;
-               LIST_INSERT_HEAD(&queue->queue, it, next);
-
                continue;
 
 skip_item:
-               warn("reading queue: `%s'", queuefn);
-               if (sender != NULL)
-                       free(sender);
+               syslog(LOG_INFO, "could not pick up queue file: `%s'/`%s': %m", queuefn, mailfn);
                if (queuefn != NULL)
                        free(queuefn);
                if (mailfn != NULL)
-                       free(queuefn);
-               if (queueid != NULL)
-                       free(queueid);
-               if (queuef != NULL)
-                       fclose(queuef);
-               if (mailf != NULL)
-                       fclose(mailf);
-               close(fd);
+                       free(mailfn);
        }
        closedir(spooldir);
-       return;
+       return (0);
 
 fail:
-       err(1, "reading queue");
+       return (-1);
 }
 
 void
 delqueue(struct qitem *it)
 {
-       unlink(it->queuefn);
-       close(it->queuefd);
        unlink(it->mailfn);
-       fclose(it->mailf);
+       unlink(it->queuefn);
+       if (it->queuef != NULL)
+               fclose(it->queuef);
+       if (it->mailf != NULL)
+               fclose(it->mailf);
        free(it);
 }
+
+int
+acquirespool(struct qitem *it)
+{
+       int queuefd;
+
+       if (it->queuef == NULL) {
+               queuefd = open_locked(it->queuefn, O_RDWR|O_NONBLOCK);
+               if (queuefd < 0)
+                       goto fail;
+               it->queuef = fdopen(queuefd, "r+");
+               if (it->queuef == NULL)
+                       goto fail;
+       }
+
+       if (it->mailf == NULL) {
+               it->mailf = fopen(it->mailfn, "r");
+               if (it->mailf == NULL)
+                       goto fail;
+       }
+
+       return (0);
+
+fail:
+       if (errno == EWOULDBLOCK)
+               return (1);
+       syslog(LOG_INFO, "could not acquire queue file: %m");
+       return (-1);
+}
+
+void
+dropspool(struct queue *queue, struct qitem *keep)
+{
+       struct qitem *it;
+
+       LIST_FOREACH(it, &queue->queue, next) {
+               if (it == keep)
+                       continue;
+
+               if (it->queuef != NULL)
+                       fclose(it->queuef);
+               if (it->mailf != NULL)
+                       fclose(it->mailf);
+       }
+}
+
+int
+flushqueue_since(unsigned int period)
+{
+        struct stat st;
+       struct timeval now;
+        char *flushfn = NULL;
+
+       if (asprintf(&flushfn, "%s/%s", config.spooldir, SPOOL_FLUSHFILE) < 0)
+               return (0);
+       if (stat(flushfn, &st) < 0) {
+               free(flushfn);
+               return (0);
+       }
+       free(flushfn);
+       flushfn = NULL;
+       if (gettimeofday(&now, 0) != 0)
+               return (0);
+
+       /* Did the flush file get touched within the last period seconds? */
+       if (st.st_mtim.tv_sec + period >= now.tv_sec)
+               return (1);
+       else
+               return (0);
+}
+
+int
+flushqueue_signal(void)
+{
+        char *flushfn = NULL;
+       int fd;
+
+        if (asprintf(&flushfn, "%s/%s", config.spooldir, SPOOL_FLUSHFILE) < 0)
+               return (-1);
+       fd = open(flushfn, O_CREAT|O_WRONLY|O_TRUNC, 0660);
+       free(flushfn);
+       if (fd < 0) {
+               syslog(LOG_ERR, "could not open flush file: %m");
+               return (-1);
+       }
+        close(fd);
+       return (0);
+}