NetBSD/gnu/dist/postfix/global/mail_queue.c

394 lines
11 KiB
C

/*++
/* NAME
/* mail_queue 3
/* SUMMARY
/* mail queue file access
/* SYNOPSIS
/* #include <mail_queue.h>
/*
/* VSTREAM *mail_queue_enter(queue_name, mode)
/* const char *queue_name;
/* int mode;
/*
/* VSTREAM *mail_queue_open(queue_name, queue_id, flags, mode)
/* const char *queue_name;
/* const char *queue_id;
/* int flags;
/* int 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.
/*
/* 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() renames 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>
/* Global library. */
#include "file_id.h"
#include "mail_params.h"
#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)
{
char *myname = "mail_queue_dir";
static VSTRING *private_buf = 0;
static VSTRING *hash_buf = 0;
static ARGV *hash_queue_names = 0;
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, " \t\r\n,");
}
/*
* 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) {
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)
{
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 = 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 = 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;
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;
for (cp = queue_id; *cp; cp++)
if (!ISALNUM(*cp))
return (0);
return (1);
}
/* mail_queue_enter - make mail queue entry with locally-unique name */
VSTREAM *mail_queue_enter(const char *queue_name, int mode)
{
char *myname = "mail_queue_enter";
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();
id_buf = vstring_alloc(10);
path_buf = vstring_alloc(10);
temp_path = vstring_alloc(100);
}
GETTIMEOFDAY(&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 (;;) {
vstring_sprintf(temp_path, "%s/%d.%d", queue_name,
(int) tv.tv_usec, pid);
if ((fd = open(STR(temp_path), O_RDWR | O_CREAT | O_EXCL, mode)) >= 0)
break;
if (errno == EEXIST || errno == EISDIR) {
if ((int) ++tv.tv_usec < 0)
tv.tv_usec = 0;
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);
GETTIMEOFDAY(&tv);
for (count = 0;; count++) {
vstring_sprintf(id_buf, "%05X%s", (int) tv.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. */
if ((int) ++tv.tv_usec < 0)
tv.tv_usec = 0;
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, VSTREAM_CTL_PATH, STR(path_buf), 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, int 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 ((flags & O_CREAT) == O_CREAT && mail_queue_mkdirs(path) == 0)
fp = vstream_fopen(path, flags, mode);
return (fp);
}