summaryrefslogtreecommitdiffstats
path: root/src/global/mail_queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/mail_queue.c')
-rw-r--r--src/global/mail_queue.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/global/mail_queue.c b/src/global/mail_queue.c
new file mode 100644
index 0000000..f73d1f1
--- /dev/null
+++ b/src/global/mail_queue.c
@@ -0,0 +1,439 @@
+/*++
+/* NAME
+/* mail_queue 3
+/* SUMMARY
+/* mail queue file access
+/* SYNOPSIS
+/* #include <mail_queue.h>
+/*
+/* VSTREAM *mail_queue_enter(queue_name, mode, tp)
+/* const char *queue_name;
+/* mode_t mode;
+/* struct timeval *tp;
+/*
+/* VSTREAM *mail_queue_open(queue_name, queue_id, flags, mode)
+/* const char *queue_name;
+/* const char *queue_id;
+/* int flags;
+/* mode_t mode;
+/*
+/* char *mail_queue_dir(buf, queue_name, queue_id)
+/* VSTRING *buf;
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* char *mail_queue_path(buf, queue_name, queue_id)
+/* VSTRING *buf;
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* int mail_queue_mkdirs(path)
+/* const char *path;
+/*
+/* int mail_queue_rename(queue_id, old_queue, new_queue)
+/* const char *queue_id;
+/* const char *old_queue;
+/* const char *new_queue;
+/*
+/* int mail_queue_remove(queue_name, queue_id)
+/* const char *queue_name;
+/* const char *queue_id;
+/*
+/* int mail_queue_name_ok(queue_name)
+/* const char *queue_name;
+/*
+/* int mail_queue_id_ok(queue_id)
+/* const char *queue_id;
+/* DESCRIPTION
+/* This module encapsulates access to the mail queue hierarchy.
+/* Unlike most other modules, this one does not abort the program
+/* in case of file access problems. But it does abort when the
+/* application attempts to use a malformed queue name or queue id.
+/*
+/* mail_queue_enter() creates an entry in the named queue. The queue
+/* id is the file base name, see VSTREAM_PATH(). Queue ids are
+/* relatively short strings and are recycled in the course of time.
+/* The only guarantee given is that on a given machine, no two queue
+/* entries will have the same queue ID at the same time. The tp
+/* argument, if not a null pointer, receives the time stamp that
+/* corresponds with the queue ID.
+/*
+/* mail_queue_open() opens the named queue file. The \fIflags\fR
+/* and \fImode\fR arguments are as with open(2). The result is a
+/* null pointer in case of problems.
+/*
+/* mail_queue_dir() returns the directory name of the specified queue
+/* file. When a null result buffer pointer is provided, the result is
+/* written to a private buffer that may be overwritten upon the next
+/* call.
+/*
+/* mail_queue_path() returns the pathname of the specified queue
+/* file. When a null result buffer pointer is provided, the result
+/* is written to a private buffer that may be overwritten upon the
+/* next call.
+/*
+/* mail_queue_mkdirs() creates missing parent directories
+/* for the file named in \fBpath\fR. A non-zero result means
+/* that the operation failed.
+/*
+/* mail_queue_rename() renames a queue file. A non-zero result
+/* means the operation failed.
+/*
+/* mail_queue_remove() removes the named queue file. A non-zero result
+/* means the operation failed.
+/*
+/* mail_queue_name_ok() validates a mail queue name and returns
+/* non-zero (true) if the name contains no nasty characters.
+/*
+/* mail_queue_id_ok() does the same thing for mail queue ID names.
+/* DIAGNOSTICS
+/* Panic: invalid queue name or id given to mail_queue_path(),
+/* mail_queue_rename(), or mail_queue_remove().
+/* Fatal error: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h> /* rename() */
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h> /* gettimeofday, not in POSIX */
+#include <string.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <dir_forest.h>
+#include <make_dirs.h>
+#include <split_at.h>
+#include <sane_fsops.h>
+#include <valid_hostname.h>
+
+/* Global library. */
+
+#include "file_id.h"
+#include "mail_params.h"
+#define MAIL_QUEUE_INTERNAL
+#include "mail_queue.h"
+
+#define STR vstring_str
+
+/* mail_queue_dir - construct mail queue directory name */
+
+const char *mail_queue_dir(VSTRING *buf, const char *queue_name,
+ const char *queue_id)
+{
+ const char *myname = "mail_queue_dir";
+ static VSTRING *private_buf = 0;
+ static VSTRING *hash_buf = 0;
+ static ARGV *hash_queue_names = 0;
+ static VSTRING *usec_buf = 0;
+ const char *delim;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (mail_queue_name_ok(queue_name) == 0)
+ msg_panic("%s: bad queue name: %s", myname, queue_name);
+ if (mail_queue_id_ok(queue_id) == 0)
+ msg_panic("%s: bad queue id: %s", myname, queue_id);
+
+ /*
+ * Initialize.
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(100);
+ buf = private_buf;
+ }
+ if (hash_buf == 0) {
+ hash_buf = vstring_alloc(100);
+ hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP);
+ }
+
+ /*
+ * First, put the basic queue directory name into place.
+ */
+ vstring_strcpy(buf, queue_name);
+ vstring_strcat(buf, "/");
+
+ /*
+ * Then, see if we need to append a little directory forest.
+ */
+ for (cpp = hash_queue_names->argv; *cpp; cpp++) {
+ if (strcasecmp(*cpp, queue_name) == 0) {
+ if (MQID_FIND_LG_INUM_SEPARATOR(delim, queue_id)) {
+ if (usec_buf == 0)
+ usec_buf = vstring_alloc(20);
+ MQID_LG_GET_HEX_USEC(usec_buf, delim);
+ queue_id = STR(usec_buf);
+ }
+ vstring_strcat(buf,
+ dir_forest(hash_buf, queue_id, var_hash_queue_depth));
+ break;
+ }
+ }
+ return (STR(buf));
+}
+
+/* mail_queue_path - map mail queue id to path name */
+
+const char *mail_queue_path(VSTRING *buf, const char *queue_name,
+ const char *queue_id)
+{
+ static VSTRING *private_buf = 0;
+
+ /*
+ * Initialize.
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(100);
+ buf = private_buf;
+ }
+
+ /*
+ * Append the queue id to the possibly hashed queue directory.
+ */
+ (void) mail_queue_dir(buf, queue_name, queue_id);
+ vstring_strcat(buf, queue_id);
+ return (STR(buf));
+}
+
+/* mail_queue_mkdirs - fill in missing directories */
+
+int mail_queue_mkdirs(const char *path)
+{
+ const char *myname = "mail_queue_mkdirs";
+ char *saved_path = mystrdup(path);
+ int ret;
+
+ /*
+ * Truncate a copy of the pathname (for safety sake), and create the
+ * missing directories.
+ */
+ if (split_at_right(saved_path, '/') == 0)
+ msg_panic("%s: no slash in: %s", myname, saved_path);
+ ret = make_dirs(saved_path, 0700);
+ myfree(saved_path);
+ return (ret);
+}
+
+/* mail_queue_rename - move message to another queue */
+
+int mail_queue_rename(const char *queue_id, const char *old_queue,
+ const char *new_queue)
+{
+ VSTRING *old_buf = vstring_alloc(100);
+ VSTRING *new_buf = vstring_alloc(100);
+ int error;
+
+ /*
+ * Try the operation. If it fails, see if it is because of missing
+ * intermediate directories.
+ */
+ error = sane_rename(mail_queue_path(old_buf, old_queue, queue_id),
+ mail_queue_path(new_buf, new_queue, queue_id));
+ if (error != 0 && mail_queue_mkdirs(STR(new_buf)) == 0)
+ error = sane_rename(STR(old_buf), STR(new_buf));
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(old_buf);
+ vstring_free(new_buf);
+
+ return (error);
+}
+
+/* mail_queue_remove - remove mail queue file */
+
+int mail_queue_remove(const char *queue_name, const char *queue_id)
+{
+ return (REMOVE(mail_queue_path((VSTRING *) 0, queue_name, queue_id)));
+}
+
+/* mail_queue_name_ok - validate mail queue name */
+
+int mail_queue_name_ok(const char *queue_name)
+{
+ const char *cp;
+
+ if (*queue_name == 0 || strlen(queue_name) > 100)
+ return (0);
+
+ for (cp = queue_name; *cp; cp++)
+ if (!ISALNUM(*cp))
+ return (0);
+ return (1);
+}
+
+/* mail_queue_id_ok - validate mail queue id */
+
+int mail_queue_id_ok(const char *queue_id)
+{
+ const char *cp;
+
+ /*
+ * A file name is either a queue ID (short alphanumeric string in
+ * time+inum form) or a fast flush service logfile name (destination
+ * domain name with non-alphanumeric characters replaced by "_").
+ */
+ if (*queue_id == 0 || strlen(queue_id) > VALID_HOSTNAME_LEN)
+ return (0);
+
+ /*
+ * OK if in time+inum form or in host_domain_tld form.
+ */
+ for (cp = queue_id; *cp; cp++)
+ if (!ISALNUM(*cp) && *cp != '_')
+ return (0);
+ return (1);
+}
+
+/* mail_queue_enter - make mail queue entry with locally-unique name */
+
+VSTREAM *mail_queue_enter(const char *queue_name, mode_t mode,
+ struct timeval * tp)
+{
+ const char *myname = "mail_queue_enter";
+ static VSTRING *sec_buf;
+ static VSTRING *usec_buf;
+ static VSTRING *id_buf;
+ static int pid;
+ static VSTRING *path_buf;
+ static VSTRING *temp_path;
+ struct timeval tv;
+ int fd;
+ const char *file_id;
+ VSTREAM *stream;
+ int count;
+
+ /*
+ * Initialize.
+ */
+ if (id_buf == 0) {
+ pid = getpid();
+ sec_buf = vstring_alloc(10);
+ usec_buf = vstring_alloc(10);
+ id_buf = vstring_alloc(10);
+ path_buf = vstring_alloc(10);
+ temp_path = vstring_alloc(100);
+ }
+ if (tp == 0)
+ tp = &tv;
+
+ /*
+ * Create a file with a temporary name that does not collide. The process
+ * ID alone is not sufficiently unique: maildrops can be shared via the
+ * network. Not that I recommend using a network-based queue, or having
+ * multiple hosts write to the same queue, but we should try to avoid
+ * losing mail if we can.
+ *
+ * If someone is racing against us, try to win.
+ */
+ for (;;) {
+ GETTIMEOFDAY(tp);
+ vstring_sprintf(temp_path, "%s/%d.%d", queue_name,
+ (int) tp->tv_usec, pid);
+ if ((fd = open(STR(temp_path), O_RDWR | O_CREAT | O_EXCL, mode)) >= 0)
+ break;
+ if (errno == EEXIST || errno == EISDIR)
+ continue;
+ msg_warn("%s: create file %s: %m", myname, STR(temp_path));
+ sleep(10);
+ }
+
+ /*
+ * Rename the file to something that is derived from the file ID. I saw
+ * this idea first being used in Zmailer. On any reasonable file system
+ * the file ID is guaranteed to be unique. Better let the OS resolve
+ * collisions than doing a worse job in an application. Another
+ * attractive property of file IDs is that they can appear in messages
+ * without leaking a significant amount of system information (unlike
+ * process ids). Not so nice is that files need to be renamed when they
+ * are moved to another file system.
+ *
+ * If someone is racing against us, try to win.
+ */
+ file_id = get_file_id_fd(fd, var_long_queue_ids);
+
+ /*
+ * XXX Some systems seem to have clocks that correlate with process
+ * scheduling or something. Unfortunately, we cannot add random
+ * quantities to the time, because the non-inode part of a queue ID must
+ * not repeat within the same second. The queue ID is the sole thing that
+ * prevents multiple messages from getting the same Message-ID value.
+ */
+ for (count = 0;; count++) {
+ GETTIMEOFDAY(tp);
+ if (var_long_queue_ids) {
+ vstring_sprintf(id_buf, "%s%s%c%s",
+ MQID_LG_ENCODE_SEC(sec_buf, tp->tv_sec),
+ MQID_LG_ENCODE_USEC(usec_buf, tp->tv_usec),
+ MQID_LG_INUM_SEP, file_id);
+ } else {
+ vstring_sprintf(id_buf, "%s%s",
+ MQID_SH_ENCODE_USEC(usec_buf, tp->tv_usec),
+ file_id);
+ }
+ mail_queue_path(path_buf, queue_name, STR(id_buf));
+ if (sane_rename(STR(temp_path), STR(path_buf)) == 0) /* success */
+ break;
+ if (errno == EPERM || errno == EISDIR) /* collision. weird. */
+ continue;
+ if (errno != ENOENT || mail_queue_mkdirs(STR(path_buf)) < 0) {
+ msg_warn("%s: rename %s to %s: %m", myname,
+ STR(temp_path), STR(path_buf));
+ }
+ if (count > 1000) /* XXX whatever */
+ msg_fatal("%s: rename %s to %s: giving up", myname,
+ STR(temp_path), STR(path_buf));
+ }
+
+ stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(stream, CA_VSTREAM_CTL_PATH(STR(path_buf)), CA_VSTREAM_CTL_END);
+ return (stream);
+}
+
+/* mail_queue_open - open mail queue file */
+
+VSTREAM *mail_queue_open(const char *queue_name, const char *queue_id,
+ int flags, mode_t mode)
+{
+ const char *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id);
+ VSTREAM *fp;
+
+ /*
+ * Try the operation. If file creation fails, see if it is because of a
+ * missing subdirectory.
+ */
+ if ((fp = vstream_fopen(path, flags, mode)) == 0)
+ if (errno == ENOENT)
+ if ((flags & O_CREAT) == O_CREAT && mail_queue_mkdirs(path) == 0)
+ fp = vstream_fopen(path, flags, mode);
+ return (fp);
+}