diff options
Diffstat (limited to 'contrib/dma/spool.c')
-rw-r--r-- | contrib/dma/spool.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/contrib/dma/spool.c b/contrib/dma/spool.c new file mode 100644 index 0000000..416b5fa --- /dev/null +++ b/contrib/dma/spool.c @@ -0,0 +1,440 @@ +/* + * 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>. + * + * 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. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 + * COPYRIGHT HOLDERS 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. + */ + +#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): + * Organized like an RFC822 header, field: value. Ignores unknown fields. + * ID: id + * Sender: envelope-from + * Recipient: envelope-to + * + * 'M'id files (data): + * 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 data file + * and a unique identifier. + */ + +int +newspoolf(struct queue *queue) +{ + char fn[PATH_MAX+1]; + struct stat st; + struct stritem *t; + int fd; + + if (snprintf(fn, sizeof(fn), "%s/%s", config.spooldir, "tmp_XXXXXXXXXX") <= 0) + return (-1); + + fd = mkstemp(fn); + if (fd < 0) + 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; + + /* + * Assign queue id + */ + if (fstat(fd, &st) != 0) + goto fail; + if (asprintf(&queue->id, "%"PRIxMAX, (uintmax_t)st.st_ino) < 0) + goto fail; + + queue->mailf = fdopen(fd, "r+"); + if (queue->mailf == NULL) + goto fail; + + t = malloc(sizeof(*t)); + if (t != NULL) { + t->str = queue->tmpf; + SLIST_INSERT_HEAD(&tmpfs, t, next); + } + return (0); + +fail: + 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) +{ + struct stat st; + struct qitem *it; + + if (fflush(queue->mailf) != 0 || fsync(fileno(queue->mailf)) != 0) + goto delfiles; + + 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, "%s.%"PRIxPTR, queue->id, (uintptr_t)it) <= 0) + goto delfiles; + 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) + goto delfiles; + + /* Neither file may not exist yet */ + if (stat(it->queuefn, &st) == 0 || stat(it->mailfn, &st) == 0) + goto delfiles; + + if (writequeuef(it) != 0) + goto delfiles; + + if (link(queue->tmpf, it->mailfn) != 0) + goto delfiles; + } + + 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->mailfn); + unlink(it->queuefn); + } + return (-1); +} + +int +load_queue(struct queue *queue) +{ + struct stat sb; + struct qitem *it; + DIR *spooldir; + struct dirent *de; + char *queuefn; + char *mailfn; + + bzero(queue, sizeof(*queue)); + LIST_INIT(&queue->queue); + + spooldir = opendir(config.spooldir); + if (spooldir == NULL) + err(1, "reading queue"); + + while ((de = readdir(spooldir)) != NULL) { + queuefn = NULL; + mailfn = NULL; + + /* ignore non-queue files */ + if (de->d_name[0] != 'Q') + continue; + 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) + goto fail; + + /* + * 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; + if (!S_ISREG(sb.st_mode)) { + errno = EINVAL; + goto skip_item; + } + + if (stat(mailfn, &sb) != 0) + goto skip_item; + + it = readqueuef(queue, queuefn); + if (it == NULL) + goto skip_item; + + it->mailfn = mailfn; + continue; + +skip_item: + syslog(LOG_INFO, "could not pick up queue file: `%s'/`%s': %m", queuefn, mailfn); + if (queuefn != NULL) + free(queuefn); + if (mailfn != NULL) + free(mailfn); + } + closedir(spooldir); + return (0); + +fail: + return (-1); +} + +void +delqueue(struct qitem *it) +{ + unlink(it->mailfn); + 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); +} |