* 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) {
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");
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);
+}